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
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ private async Task InspectDirectiveTypeAsync()
},
{
"name": "isRepeatable" # <--- and we are looking for this!
},
{
"name": "isDeprecated" # <--- and for this!
}
]
}
Expand Down Expand Up @@ -145,6 +148,7 @@ private async Task InspectDirectiveTypeAsync()
{
var locations = false;
var isRepeatable = false;
var isDeprecated = false;

foreach (var field in fields.EnumerateArray())
{
Expand All @@ -168,7 +172,13 @@ private async Task InspectDirectiveTypeAsync()
_features.HasRepeatableDirectives = true;
}

if (locations && isRepeatable)
if (fieldNameString.EqualsOrdinal("isDeprecated"))
{
isDeprecated = true;
_features.HasDirectiveDeprecation = true;
}

if (locations && isRepeatable && isDeprecated)
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,9 @@ private static DirectiveDefinitionNode CreateDirectiveDefinition(
CreateDescription(directive.Description),
directive.IsRepeatable ?? false,
CreateInputValues(directive.Args),
[],
CreateDeprecatedDirective(
directive.IsDeprecated,
directive.DeprecationReason),
locations
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ public static DocumentNode Build(ServerCapabilities features, IntrospectionOptio

selections.Add(CreateTypesField());

selections.Add(
CreateDirectivesField(
features.HasDirectiveLocations,
features.HasRepeatableDirectives));
selections.Add(CreateDirectivesField(features));

return new DocumentNode(
new IDefinitionNode[]
Expand Down Expand Up @@ -103,28 +100,16 @@ private static FieldNode CreateTypesField()
Array.Empty<DirectiveNode>())
}));

private static FieldNode CreateDirectivesField(bool hasLocationsField, bool hasRepeatableDirective)
private static FieldNode CreateDirectivesField(ServerCapabilities features)
{
var selections = new List<ISelectionNode>
{
new FieldNode("name"),
new FieldNode("description"),
new FieldNode(
new NameNode("args"),
null,
Array.Empty<DirectiveNode>(),
Array.Empty<ArgumentNode>(),
new SelectionSetNode(
new ISelectionNode[]
{
new FragmentSpreadNode(
null,
new NameNode("InputValue"),
Array.Empty<DirectiveNode>())
}))
CreateArgsField(features.HasArgumentDeprecation)
};

if (hasLocationsField)
if (features.HasDirectiveLocations)
{
selections.Add(new FieldNode("locations"));
}
Expand All @@ -135,16 +120,24 @@ private static FieldNode CreateDirectivesField(bool hasLocationsField, bool hasR
selections.Add(new FieldNode("onField"));
}

if (hasRepeatableDirective)
if (features.HasRepeatableDirectives)
{
selections.Add(new FieldNode("isRepeatable"));
}

if (features.HasDirectiveDeprecation)
{
selections.Add(new FieldNode("isDeprecated"));
selections.Add(new FieldNode("deprecationReason"));
}

return new FieldNode(
new NameNode("directives"),
null,
Array.Empty<DirectiveNode>(),
Array.Empty<ArgumentNode>(),
features.HasDirectiveDeprecation
? [new ArgumentNode("includeDeprecated", true)]
: Array.Empty<ArgumentNode>(),
new SelectionSetNode(selections));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ internal class Directive
public ICollection<InputField> Args { get; set; }
public ICollection<string> Locations { get; set; }
public bool? IsRepeatable { get; set; }
public bool IsDeprecated { get; set; }
public string DeprecationReason { get; set; }
public bool OnOperation { get; set; }
public bool OnFragment { get; set; }
public bool OnField { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public class ServerCapabilities
/// </summary>
public bool HasArgumentDeprecation { get; internal set; }

/// <summary>
/// Gets a value that defines if the GraphQL server supports directive definition deprecation.
/// </summary>
public bool HasDirectiveDeprecation { get; internal set; }

/// <summary>
/// Gets a value that defines if the GraphQL server supports schema descriptions.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System.Net;
using HotChocolate.AspNetCore.Tests.Utilities;
using HotChocolate.Language;
using HotChocolate.Language.Utilities;
using HotChocolate.Types;
using Microsoft.Extensions.DependencyInjection;

// ReSharper disable AccessToDisposedClosure

Expand All @@ -19,13 +23,19 @@ public async Task InspectServer()
var features = await IntrospectionClient.InspectServerAsync(client, TestContext.Current.CancellationToken);

// assert
Assert.True(features.HasArgumentDeprecation);
Assert.True(features.HasDirectiveLocations);
Assert.True(features.HasSubscriptionSupport);
Assert.True(features.HasSchemaDescription);
Assert.True(features.HasRepeatableDirectives);
Assert.True(features.HasDeferSupport);
Assert.True(features.HasStreamSupport);
features.MatchInlineSnapshot(
"""
{
"HasDirectiveLocations": true,
"HasRepeatableDirectives": true,
"HasSubscriptionSupport": true,
"HasDeferSupport": true,
"HasStreamSupport": true,
"HasArgumentDeprecation": true,
"HasDirectiveDeprecation": true,
"HasSchemaDescription": true
}
""");
}

[Fact]
Expand Down Expand Up @@ -54,6 +64,32 @@ public async Task IntrospectServer()
schema.ToString(true).MatchSnapshot();
}

[Fact]
public async Task IntrospectServer_With_DeprecatedDirective()
{
// arrange
var server = CreateStarWarsServer(
configureServices: services => services
.AddGraphQL()
.AddDirectiveType<ObsoleteDirectiveType>());
Comment thread
glen-84 marked this conversation as resolved.
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost:5000/graphql");

// act
var schema = await IntrospectionClient.IntrospectServerAsync(client, TestContext.Current.CancellationToken);

// assert
var directive = Assert.Single(
schema.Definitions.OfType<DirectiveDefinitionNode>(),
t => t.Name.Value.Equals("obsolete", StringComparison.Ordinal));
directive.Print(indented: true).MatchInlineSnapshot(
"""
directive @obsolete(
obsoleteArg: String @deprecated(reason: "Argument no longer supported.")
) @deprecated(reason: "Directive no longer supported.") on FIELD
""");
}

[Fact]
public async Task IntrospectServer_HttpClient_Is_Null()
{
Expand Down Expand Up @@ -110,6 +146,22 @@ public async Task IntrospectServer_Transport_Error()
Assert.Equal("Something went wrong", exception.Message);
}

private sealed class ObsoleteDirectiveType : DirectiveType
{
protected override void Configure(IDirectiveTypeDescriptor descriptor)
{
descriptor
.Name("obsolete")
.Location(Types.DirectiveLocation.Field)
.Deprecated("Directive no longer supported.");

descriptor
.Argument("obsoleteArg")
.Type<StringType>()
.Deprecated("Argument no longer supported.");
}
}

private class CustomHttpClientHandler(HttpStatusCode? httpStatusCode = null) : HttpClientHandler
{
protected override Task<HttpResponseMessage> SendAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,36 @@ public void DeserializeIntrospectionWithIntDefaultValues()
schema.ToString(true).MatchSnapshot();
}

[Fact]
public void DeserializeIntrospectionWithDeprecatedDirectives()
{
// arrange
var json = FileResource.Open("IntrospectionWithDeprecatedDirectives.json");
var result = JsonSerializer.Deserialize<IntrospectionResult>(json, SerializerOptions);

// act
var schema = IntrospectionFormatter.Format(result!);

// assert
schema.ToString(true).MatchInlineSnapshot(
"""
schema {
query: Query
}

type Query {
field: String
}

"Built-in String"
scalar String

directive @obsolete(
obsoleteArg: String @deprecated(reason: "Argument no longer supported.")
) @deprecated(reason: "Directive no longer supported.") on FIELD
""");
}

[Fact]
public void DeserializeIntrospectionWithNullDeprecationReason()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ public void Create_Query_With_ArgumentDeprecation()
document.Print().MatchSnapshot();
}

[Fact]
public void Create_Query_With_DirectiveDeprecation()
{
// arrange
var options = new IntrospectionOptions();
var features = new ServerCapabilities
{
HasDirectiveDeprecation = true
};

// act
var document = IntrospectionQueryBuilder.Build(features, options);

//assert
document.Print().MatchSnapshot();
}

[Fact]
public void Create_Query_With_DirectiveLocations()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"data": {
"__schema": {
"queryType": {
"name": "Query"
},
"mutationType": null,
"subscriptionType": null,
"types": [{
"kind": "OBJECT",
"name": "Query",
"description": null,
"fields": [{
"name": "field",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "String",
"description": "Built-in String",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
}
],
"directives": [{
"name": "obsolete",
"description": null,
"locations": ["FIELD"],
"args": [{
"name": "obsoleteArg",
"description": null,
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null,
"isDeprecated": true,
"deprecationReason": "Argument no longer supported."
}
],
"isRepeatable": false,
"isDeprecated": true,
"deprecationReason": "Directive no longer supported."
}
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ query IntrospectionQuery {
directives {
name
description
args {
args(includeDeprecated: true) {
...InputValue
isDeprecated
deprecationReason
}
onOperation
onFragment
Expand Down
Loading
Loading