Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds deprecation of input fields and arguments #4630

Merged
merged 24 commits into from
Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1db5e1d
Added Deprecation of input fields
PascalSenn Jan 9, 2022
e2fe06f
Update Snapshots
PascalSenn Jan 9, 2022
0fdaf63
Update src/HotChocolate/Core/src/Types/Types/Introspection/__Type.cs
michaelstaib Jan 10, 2022
fdb3572
Add tests for validation rules
PascalSenn Jan 11, 2022
1d6428d
Resolve comments
PascalSenn Jan 11, 2022
fb4d3ff
Merge branch 'pse/deprecation-of-input-fields' of github.com:ChilliCr…
PascalSenn Jan 11, 2022
d82adbc
Fix format
PascalSenn Jan 12, 2022
6f56276
Fix format
PascalSenn Jan 12, 2022
4062541
Algin loop format
PascalSenn Jan 12, 2022
6a4944a
Algin loop format
PascalSenn Jan 12, 2022
d41daec
Fix csproj
PascalSenn Jan 12, 2022
0dd34a2
Merge branch 'main' into pse/deprecation-of-input-fields
PascalSenn Jan 12, 2022
580465d
Merge branch 'main' into pse/deprecation-of-input-fields
michaelstaib Jan 28, 2022
b819ac9
Added test for input fields
michaelstaib Jan 28, 2022
ec72764
Merge branch 'main' into pse/deprecation-of-input-fields
PascalSenn Feb 2, 2022
f568ee3
Add Tests for directives
PascalSenn Feb 6, 2022
0876a50
Add Tests for inputtypes
PascalSenn Feb 6, 2022
272cff1
Add Tests for interfaces
PascalSenn Feb 6, 2022
76f0300
Add Tests for object types
PascalSenn Feb 6, 2022
7d16e3b
Merge branch 'pse/deprecation-of-input-fields' of github.com:ChilliCr…
PascalSenn Feb 6, 2022
0119e0c
Update tests
PascalSenn Feb 6, 2022
64a82ff
Fixed [Obsolete] on directive argument
PascalSenn Feb 6, 2022
12f6d6f
Merge branch 'main' into pse/deprecation-of-input-fields
PascalSenn Feb 6, 2022
50d8baa
Update ResolveObjectFieldDescriptorExtensions.cs
michaelstaib Feb 7, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace HotChocolate;
/// </summary>
[AttributeUsage(AttributeTargets.Field // Required for enum values
| AttributeTargets.Property
| AttributeTargets.Parameter
| AttributeTargets.Method)]
public sealed class GraphQLDeprecatedAttribute : Attribute
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Linq;
using HotChocolate.Types;
using static HotChocolate.Configuration.Validation.TypeValidationHelper;
using static HotChocolate.Configuration.Validation.ErrorHelper;

namespace HotChocolate.Configuration.Validation;

/// <summary>
/// Implements directive type validation defined in the spec.
/// http://spec.graphql.org/draft/#sec-Type-System.Directives.Validation
/// </summary>
internal class DirectiveValidationRule : ISchemaValidationRule
{
private const string _twoUnderscores = "__";

public void Validate(
IReadOnlyList<ITypeSystemObject> typeSystemObjects,
IReadOnlySchemaOptions options,
ICollection<ISchemaError> errors)
{
if (options.StrictValidation)
{
foreach (DirectiveType type in typeSystemObjects.OfType<DirectiveType>())
{
EnsureDirectiveNameIsValid(type, errors);
EnsureArgumentNamesAreValid(type, errors);
EnsureArgumentDeprecationIsValid(type, errors);
}
}
}

private static void EnsureDirectiveNameIsValid(
DirectiveType type,
ICollection<ISchemaError> errors)
{
if (type.Name.Value.StartsWith(_twoUnderscores))
{
errors.Add(TwoUnderscoresNotAllowedOnDirectiveName(type));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ internal static class ErrorHelper
private const string _interfaceTypeValidation = "sec-Interfaces.Type-Validation";
private const string _objectTypeValidation = "sec-Objects.Type-Validation";
private const string _inputObjectTypeValidation = "sec-Input-Objects.Type-Validation";
private const string _directiveValidation = "sec-Type-System.Directives.Validation";

public static ISchemaError NeedsOneAtLeastField(INamedType type)
=> SchemaErrorBuilder.New()
Expand Down Expand Up @@ -47,6 +48,25 @@ public static ISchemaError TwoUnderscoresNotAllowedOnArgument(
.SetSpecifiedBy(type.Kind)
.Build();

public static ISchemaError TwoUnderscoresNotAllowedOnArgument(
DirectiveType type,
IInputField argument)
=> SchemaErrorBuilder.New()
.SetMessage(
"Argument names starting with `__` are reserved for " +
" the GraphQL specification.")
.SetDirective(type)
.SetArgument(argument)
.SetSpecifiedBy(TypeKind.Directive)
.Build();

public static ISchemaError TwoUnderscoresNotAllowedOnDirectiveName(DirectiveType type)
=> SchemaErrorBuilder.New()
.SetMessage("Names starting with `__` are reserved for the GraphQL specification.")
.SetDirective(type)
.SetSpecifiedBy(TypeKind.Directive)
.Build();

public static ISchemaError NotTransitivelyImplemented(
IComplexOutputType type,
IComplexOutputType implementedType)
Expand Down Expand Up @@ -163,13 +183,58 @@ public static ISchemaError OneofInputObjectMustHaveNullableFieldsWithoutDefaults
.SetSpecifiedBy(type.Kind, rfc: 825)
.Build();

public static ISchemaError RequiredArgumentCannotBeDeprecated(
IComplexOutputType type,
IOutputField field,
IInputField argument)
=> SchemaErrorBuilder.New()
.SetMessage(
"Required argument {0} cannot be deprecated.",
argument.Coordinate.ToString())
.SetType(type)
.SetField(field)
.SetArgument(argument)
.SetSpecifiedBy(type.Kind, rfc: 805)
.Build();

public static ISchemaError RequiredArgumentCannotBeDeprecated(
DirectiveType directive,
IInputField argument)
=> SchemaErrorBuilder.New()
.SetMessage(
"Required argument {0} cannot be deprecated.",
argument.Coordinate.ToString())
.SetDirective(directive)
.SetArgument(argument)
.SetSpecifiedBy(TypeKind.Directive, rfc: 805)
.Build();

public static ISchemaError RequiredFieldCannotBeDeprecated(
IInputObjectType type,
IInputField field)
=> SchemaErrorBuilder.New()
.SetMessage(
"Required input field {0} cannot be deprecated.",
field.Coordinate.ToString())
.SetType(type)
.SetField(field)
.SetSpecifiedBy(TypeKind.InputObject, rfc: 805)
.Build();

private static ISchemaErrorBuilder SetType(
this ISchemaErrorBuilder errorBuilder,
INamedType type)
=> errorBuilder
.AddSyntaxNode(type.SyntaxNode)
.SetTypeSystemObject((TypeSystemObjectBase)type);

private static ISchemaErrorBuilder SetDirective(
this ISchemaErrorBuilder errorBuilder,
DirectiveType type)
=> errorBuilder
.AddSyntaxNode(type.SyntaxNode)
.SetTypeSystemObject(type);

private static ISchemaErrorBuilder SetField(
this ISchemaErrorBuilder errorBuilder,
IField field,
Expand Down Expand Up @@ -208,7 +273,8 @@ private static ISchemaErrorBuilder SetSpecifiedBy(
errorBuilder
.SpecifiedBy(_interfaceTypeValidation, kind is TypeKind.Interface)
.SpecifiedBy(_objectTypeValidation, kind is TypeKind.Object)
.SpecifiedBy(_inputObjectTypeValidation, kind is TypeKind.InputObject);
.SpecifiedBy(_inputObjectTypeValidation, kind is TypeKind.InputObject)
.SpecifiedBy(_directiveValidation, kind is TypeKind.Directive);

if (rfc.HasValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public void Validate(
EnsureTypeHasFields(type, errors);
EnsureFieldNamesAreValid(type, errors);
EnsureOneOfFieldsAreValid(type, errors);
EnsureFieldDeprecationIsValid(type, errors);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public void Validate(
EnsureTypeHasFields(type, errors);
EnsureFieldNamesAreValid(type, errors);
EnsureInterfacesAreCorrectlyImplemented(type, errors);
EnsureArgumentDeprecationIsValid(type, errors);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public void Validate(
EnsureTypeHasFields(type, errors);
EnsureFieldNamesAreValid(type, errors);
EnsureInterfacesAreCorrectlyImplemented(type, errors);
EnsureArgumentDeprecationIsValid(type, errors);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal static class SchemaValidator
new ObjectTypeValidationRule(),
new InterfaceTypeValidationRule(),
new InputObjectTypeValidationRule(),
new DirectiveValidationRule(),
new InterfaceHasAtLeastOneImplementationRule()
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,54 @@ public static void EnsureTypeHasFields(
}
}

public static void EnsureFieldDeprecationIsValid(
IInputObjectType type,
ICollection<ISchemaError> errors)
{
for (var i = 0; i < type.Fields.Count; i++)
{
IInputField field = type.Fields[i];

if (field.IsDeprecated && field.Type.IsNonNullType())
{
errors.Add(RequiredFieldCannotBeDeprecated(type, field));
}
}
}

public static void EnsureArgumentDeprecationIsValid(
IComplexOutputType type,
ICollection<ISchemaError> errors)
{
for (var i = 0; i < type.Fields.Count; i++)
{
IOutputField field = type.Fields[i];
for (var j = 0; j < field.Arguments.Count; j++)
{
IInputField argument = field.Arguments[j];

if (argument.IsDeprecated && argument.Type.IsNonNullType())
{
errors.Add(RequiredArgumentCannotBeDeprecated(type, field, argument));
}
}
}
}

public static void EnsureArgumentDeprecationIsValid(
DirectiveType type,
ICollection<ISchemaError> errors)
{
for (var i = 0; i < type.Arguments.Count; i++)
{
Argument argument = type.Arguments[i];
if (argument.IsDeprecated && argument.Type.IsNonNullType())
{
errors.Add(RequiredArgumentCannotBeDeprecated(type, argument));
}
}
}

public static void EnsureTypeHasFields(
InputObjectType type,
ICollection<ISchemaError> errors)
Expand Down Expand Up @@ -54,7 +102,9 @@ public static void EnsureFieldNamesAreValid(
if (argument.Name.Value.StartsWith(_twoUnderscores))
{
errors.Add(TwoUnderscoresNotAllowedOnArgument(
type, field, argument));
type,
field,
argument));
}
}
}
Expand All @@ -76,6 +126,21 @@ public static void EnsureFieldNamesAreValid(
}
}

public static void EnsureArgumentNamesAreValid(
DirectiveType type,
ICollection<ISchemaError> errors)
{
for (var i = 0; i < type.Arguments.Count; i++)
{
IInputField field = type.Arguments[i];

if (field.Name.Value.StartsWith(_twoUnderscores))
{
errors.Add(TwoUnderscoresNotAllowedOnArgument(type, field));
}
}
}

public static void EnsureInterfacesAreCorrectlyImplemented(
IComplexOutputType type,
ICollection<ISchemaError> errors)
Expand Down Expand Up @@ -133,22 +198,27 @@ private static void ValidateArguments(
if (!argument.Type.IsEqualTo(implementedArgument.Type))
{
errors.Add(InvalidArgumentType(
field, implementedField,
argument, implementedArgument));
field,
implementedField,
argument,
implementedArgument));
}
}
else if (argument.Type.IsNonNullType())
{
errors.Add(AdditionalArgumentNotNullable(
field, implementedField,
field,
implementedField,
argument));
}
}

foreach (IInputField? missingArgument in implArgs.Values)
{
errors.Add(ArgumentNotImplemented(
field, implementedField, missingArgument));
field,
implementedField,
missingArgument));
}
}

Expand All @@ -163,6 +233,7 @@ private static bool IsFullyImplementingInterface(
return false;
}
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ public static void MergeInputObjectFields(
IList<InputFieldDefinition> typeFields)
{
MergeFields(context, extensionFields, typeFields,
(_, _, _) => { });
(_, extensionField, typeField) =>
{
if (extensionField.IsDeprecated)
{
typeField.DeprecationReason = extensionField.DeprecationReason;
}
});
}

private static void MergeOutputFields<T>(
Expand Down
1 change: 0 additions & 1 deletion src/HotChocolate/Core/src/Types/Resolvers/ArgumentValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,4 @@ public ArgumentValue(IInputFieldInfo argument, IError error)
/// If this argument has error this represents the argument error.
/// </summary>
public IError? Error { get; }

}
17 changes: 14 additions & 3 deletions src/HotChocolate/Core/src/Types/SchemaSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,14 +473,25 @@ private static void SerializeDeprecationDirective(
}

private static InputValueDefinitionNode SerializeInputField(
IInputField inputValue) =>
new(
IInputField inputValue)
{
var directives = inputValue.Directives
.Select(SerializeDirective)
.ToList();

SerializeDeprecationDirective(
directives,
inputValue.IsDeprecated,
inputValue.DeprecationReason);

return new(
null,
new NameNode(inputValue.Name),
SerializeDescription(inputValue.Description),
SerializeType(inputValue.Type),
inputValue.DefaultValue,
inputValue.Directives.Select(SerializeDirective).ToList());
directives);
}

private static ITypeNode SerializeType(IType type)
{
Expand Down
9 changes: 9 additions & 0 deletions src/HotChocolate/Core/src/Types/Types/Argument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public Argument(ArgumentDefinition definition, int index)
{
Formatter = new AggregateInputValueFormatter(formatters);
}

IsDeprecated = !string.IsNullOrEmpty(definition.DeprecationReason);
DeprecationReason = definition.DeprecationReason;
}

/// <summary>
Expand All @@ -52,6 +55,12 @@ public Argument(ArgumentDefinition definition, int index)
/// <inheritdoc />
public IInputType Type { get; private set; } = default!;

/// <inheritdoc />
public bool IsDeprecated { get; }

/// <inheritdoc />
public string? DeprecationReason { get; }

/// <inheritdoc />
public override Type RuntimeType => _runtimeType;

Expand Down
Loading