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 @@ -281,6 +281,32 @@ public async Task Download_GraphQL_Schema_Does_Not_Include_Internal_Directives()
result.MatchSnapshot();
}

[Fact]
public async Task Download_GraphQL_Schema_Includes_Internal_Directives_When_DisableInternalDirectives_Is_True()
{
// arrange
var server = ServerFactory.Create(
services => services
.AddRouting()
.AddGraphQLServer()
.ModifyOptions(o => o.DisableInternalDirectives = true)
.AddDirectiveType<InternalDirectiveType>()
.AddQueryType<DirectiveQueryType>(),
app => app
.UseRouting()
.UseEndpoints(endpoints => endpoints.MapGraphQLSchema()));
var url = TestServerExtensions.CreateUrl("/graphql/sdl");
var request = new HttpRequestMessage(HttpMethod.Get, url);

// act
var response = await server.CreateClient().SendAsync(request);

// assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadAsStringAsync();
result.MatchSnapshot();
}

private sealed class StaticTimeProvider : ITimeProvider
{
public DateTimeOffset UtcNow { get; } = new(2021, 1, 1, 0, 0, 0, TimeSpan.Zero);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,32 @@ public async Task Download_GraphQL_SemanticNonNull_Schema_Does_Not_Include_Inter
result.MatchSnapshot();
}

[Fact]
public async Task Download_GraphQL_SemanticNonNull_Schema_Includes_Internal_Directives_When_DisableInternalDirectives_Is_True()
{
// arrange
var server = ServerFactory.Create(
services => services
.AddRouting()
.AddGraphQLServer()
.ModifyOptions(o => o.DisableInternalDirectives = true)
.AddDirectiveType<InternalDirectiveType>()
.AddQueryType<DirectiveQueryType>(),
app => app
.UseRouting()
.UseEndpoints(endpoints => endpoints.MapGraphQLSemanticNonNullSchema()));
var url = TestServerExtensions.CreateUrl("/graphql/semantic-non-null-schema.graphql");
var request = new HttpRequestMessage(HttpMethod.Get, url);

// act
var response = await server.CreateClient().SendAsync(request);

// assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadAsStringAsync();
result.MatchSnapshot();
}

private sealed class StaticTimeProvider : ITimeProvider
{
public DateTimeOffset UtcNow { get; } = new(2021, 1, 1, 0, 0, 0, TimeSpan.Zero);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
schema {
query: Query
}

type Query {
secret: String! @internal @cost(weight: "10")
public: String! @cost(weight: "10")
}

"The purpose of the `cost` directive is to define a `weight` for GraphQL types, fields, and arguments. Static analysis can use these weights when calculating the overall cost of a query or response."
directive @cost(
"The `weight` argument defines what value to add to the overall cost for every appearance, or possible appearance, of a type, field, argument, etc."
weight: String!
) on
| SCALAR
| OBJECT
| FIELD_DEFINITION
| ARGUMENT_DEFINITION
| ENUM
| INPUT_FIELD_DEFINITION

directive @internal on FIELD_DEFINITION
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schema {
query: Query
}

type Query {
secret: String @internal @cost(weight: "10") @semanticNonNull
public: String @cost(weight: "10") @semanticNonNull
}

"The purpose of the `cost` directive is to define a `weight` for GraphQL types, fields, and arguments. Static analysis can use these weights when calculating the overall cost of a query or response."
directive @cost(
"The `weight` argument defines what value to add to the overall cost for every appearance, or possible appearance, of a type, field, argument, etc."
weight: String!
) on
| SCALAR
| OBJECT
| FIELD_DEFINITION
| ARGUMENT_DEFINITION
| ENUM
| INPUT_FIELD_DEFINITION

directive @internal on FIELD_DEFINITION

directive @semanticNonNull(levels: [Int!] = [0]) on FIELD_DEFINITION
7 changes: 7 additions & 0 deletions src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ public interface IReadOnlySchemaOptions
/// </summary>
DirectiveVisibility DefaultDirectiveVisibility { get; }

/// <summary>
/// Disables the concept of internal directives so that all directives are treated as public.
/// When set to <c>true</c>, this overrides any explicit <c>Internal()</c> calls on
/// directive types and the <see cref="DefaultDirectiveVisibility"/> setting.
/// </summary>
bool DisableInternalDirectives { get; }

/// <summary>
/// Defines that the default resolver execution strategy.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions src/HotChocolate/Core/src/Types/SchemaOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ public FieldBindingFlags DefaultFieldBindingFlags
public DirectiveVisibility DefaultDirectiveVisibility { get; set; } =
DirectiveVisibility.Public;

/// <inheritdoc cref="IReadOnlySchemaOptions.DisableInternalDirectives"/>
public bool DisableInternalDirectives { get; set; }

/// <inheritdoc cref="IReadOnlySchemaOptions.DefaultResolverStrategy"/>
public ExecutionStrategy DefaultResolverStrategy { get; set; } =
ExecutionStrategy.Parallel;
Expand Down Expand Up @@ -207,6 +210,7 @@ public static SchemaOptions FromOptions(IReadOnlySchemaOptions options)
DefaultBindingBehavior = options.DefaultBindingBehavior,
EnableDirectiveIntrospection = options.EnableDirectiveIntrospection,
DefaultDirectiveVisibility = options.DefaultDirectiveVisibility,
DisableInternalDirectives = options.DisableInternalDirectives,
DefaultResolverStrategy = options.DefaultResolverStrategy,
ValidatePipelineOrder = options.ValidatePipelineOrder,
StrictRuntimeTypeValidation = options.StrictRuntimeTypeValidation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ protected override void OnCompleteType(

Locations = configuration.Locations;
Arguments = OnCompleteFields(context, configuration);
IsPublic = configuration.IsPublic;
IsPublic = configuration.IsPublic
|| context.DescriptorContext.Options.DisableInternalDirectives;
Middleware = OnCompleteMiddleware(context, configuration);

_createInstance = OnCompleteCreateInstance(context, configuration);
Expand Down
54 changes: 54 additions & 0 deletions src/HotChocolate/Core/test/Execution.Tests/IntrospectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,60 @@ directive @bar(baz: String, quox: SomeInput) repeatable on FIELD_DEFINITION
result.MatchSnapshot();
}

[Fact]
public async Task DirectiveIntrospection_AllDirectives_Public_When_DisableInternalDirectives_Is_True()
{
var result =
await new ServiceCollection()
.AddGraphQL()
.AddDocumentFromString(
"""
type Query {
foo: String
@foo
@bar(baz: "ABC")
@bar(baz: null)
@bar(quox: { a: "ABC" })
@bar(quox: { a: "DEF" })
@bar
}

input SomeInput {
a: String!
}

directive @bar(baz: String, quox: SomeInput) repeatable on FIELD_DEFINITION
""")
.UseField(next => next)
.ModifyOptions(o => o.EnableDirectiveIntrospection = true)
.ModifyOptions(o => o.DefaultDirectiveVisibility = DirectiveVisibility.Internal)
.ModifyOptions(o => o.DisableInternalDirectives = true)
.AddDirectiveType(new DirectiveType(d =>
{
d.Name("foo");
d.Location(DirectiveLocation.FieldDefinition);
d.Internal();
}))
.ExecuteRequestAsync(
@"{
__schema {
types {
fields {
appliedDirectives {
name
args {
name
value
}
}
}
}
}
}");

result.MatchSnapshot();
}

private static Schema CreateSchema()
{
return SchemaBuilder.New()
Expand Down
Loading
Loading