diff --git a/src/HotChocolate/Caching/src/Caching/CacheControlAttribute.cs b/src/HotChocolate/Caching/src/Caching/CacheControlAttribute.cs
index cd3cb657324..387dddcda0e 100644
--- a/src/HotChocolate/Caching/src/Caching/CacheControlAttribute.cs
+++ b/src/HotChocolate/Caching/src/Caching/CacheControlAttribute.cs
@@ -56,14 +56,14 @@ protected internal override void TryConfigure(
///
/// The maximum time, in seconds, this resource can be cached.
///
- public int MaxAge { get => _maxAge ?? 0; set => _maxAge = value; }
+ public int MaxAge { get => _maxAge ?? CacheControlDefaults.MaxAge; set => _maxAge = value; }
///
/// The scope of this resource.
///
public CacheControlScope Scope
{
- get => _scope ?? CacheControlScope.Public;
+ get => _scope ?? CacheControlDefaults.Scope;
set => _scope = value;
}
diff --git a/src/HotChocolate/Caching/src/Caching/CacheControlDefaults.cs b/src/HotChocolate/Caching/src/Caching/CacheControlDefaults.cs
new file mode 100644
index 00000000000..57b2d426d75
--- /dev/null
+++ b/src/HotChocolate/Caching/src/Caching/CacheControlDefaults.cs
@@ -0,0 +1,7 @@
+namespace HotChocolate.Caching;
+
+internal static class CacheControlDefaults
+{
+ public const int MaxAge = 0;
+ public const CacheControlScope Scope = CacheControlScope.Public;
+}
diff --git a/src/HotChocolate/Caching/src/Caching/CacheControlTypeInterceptor.cs b/src/HotChocolate/Caching/src/Caching/CacheControlTypeInterceptor.cs
index c448709ad85..d5f87b689bc 100644
--- a/src/HotChocolate/Caching/src/Caching/CacheControlTypeInterceptor.cs
+++ b/src/HotChocolate/Caching/src/Caching/CacheControlTypeInterceptor.cs
@@ -94,7 +94,7 @@ private void TryApplyDefaults(RegisteredType type, ObjectTypeDefinition objectDe
{
// Each field on the query type or data resolver fields
// are treated as fields that need to be explicitly cached.
- ApplyCacheControlWithDefaultMaxAge(field);
+ ApplyCacheControlWithDefaults(field);
appliedDefaults = true;
}
}
@@ -105,16 +105,28 @@ private void TryApplyDefaults(RegisteredType type, ObjectTypeDefinition objectDe
}
}
- private void ApplyCacheControlWithDefaultMaxAge(
+ private void ApplyCacheControlWithDefaults(
OutputFieldDefinitionBase field)
{
+ var isNotDefaultScope = _cacheControlOptions.DefaultScope != CacheControlDefaults.Scope;
+
+ var arguments = new ArgumentNode[isNotDefaultScope ? 2 : 1];
+ arguments[0] = new ArgumentNode(
+ CacheControlDirectiveType.Names.MaxAgeArgName,
+ _cacheControlOptions.DefaultMaxAge);
+
+ if (isNotDefaultScope)
+ {
+ arguments[1] = new ArgumentNode(
+ CacheControlDirectiveType.Names.ScopeArgName,
+ new EnumValueNode(_cacheControlOptions.DefaultScope));
+ }
+
field.Directives.Add(
new DirectiveDefinition(
new DirectiveNode(
CacheControlDirectiveType.Names.DirectiveName,
- new ArgumentNode(
- CacheControlDirectiveType.Names.MaxAgeArgName,
- _cacheControlOptions.DefaultMaxAge))));
+ arguments)));
}
private static bool HasCacheControlDirective(ObjectFieldDefinition field)
diff --git a/src/HotChocolate/Caching/src/Caching/Options/CacheControlOptions.cs b/src/HotChocolate/Caching/src/Caching/Options/CacheControlOptions.cs
index f5c296158d9..76efdd4edd1 100644
--- a/src/HotChocolate/Caching/src/Caching/Options/CacheControlOptions.cs
+++ b/src/HotChocolate/Caching/src/Caching/Options/CacheControlOptions.cs
@@ -7,7 +7,10 @@ public sealed class CacheControlOptions : ICacheControlOptions
public bool Enable { get; set; } = true;
///
- public int DefaultMaxAge { get; set; } = 0;
+ public int DefaultMaxAge { get; set; } = CacheControlDefaults.MaxAge;
+
+ ///
+ public CacheControlScope DefaultScope { get; set; } = CacheControlDefaults.Scope;
///
public bool ApplyDefaults { get; set; } = true;
diff --git a/src/HotChocolate/Caching/src/Caching/Options/ICacheControlOptions.cs b/src/HotChocolate/Caching/src/Caching/Options/ICacheControlOptions.cs
index b6a9d94cd7c..767eb780079 100644
--- a/src/HotChocolate/Caching/src/Caching/Options/ICacheControlOptions.cs
+++ b/src/HotChocolate/Caching/src/Caching/Options/ICacheControlOptions.cs
@@ -18,9 +18,17 @@ public interface ICacheControlOptions
int DefaultMaxAge { get; }
///
- /// Denotes whether the should be applied to all
- /// fields that do not already specify a ,
- /// are fields on the Query root type or are responsible for fetching data.
+ /// The Scope that should be applied to fields,
+ /// if is true.
+ /// Defaults to Public.
+ ///
+ CacheControlScope DefaultScope { get; }
+
+ ///
+ /// Denotes whether the and
+ /// should be applied to all fields that do not already specify a
+ /// , are fields on the Query root type
+ /// or are responsible for fetching data.
///
bool ApplyDefaults { get; }
}
diff --git a/src/HotChocolate/Caching/test/Caching.Tests/CacheControlTypeInterceptorTests.cs b/src/HotChocolate/Caching/test/Caching.Tests/CacheControlTypeInterceptorTests.cs
index 33e7c4cf7df..a83ad29f520 100644
--- a/src/HotChocolate/Caching/test/Caching.Tests/CacheControlTypeInterceptorTests.cs
+++ b/src/HotChocolate/Caching/test/Caching.Tests/CacheControlTypeInterceptorTests.cs
@@ -48,6 +48,24 @@ type Query {
.MatchSnapshotAsync();
}
+ [Fact]
+ public async Task QueryFields_ApplyDefaults_DifferentDefaultScope()
+ {
+ await new ServiceCollection()
+ .AddGraphQL()
+ .AddDocumentFromString(@"
+ type Query {
+ field1: String
+ field2: String @cacheControl(maxAge: 200, scope: PUBLIC)
+ }
+ ")
+ .UseField(_ => _)
+ .AddCacheControl()
+ .ModifyCacheControlOptions(o => o.DefaultScope = CacheControlScope.Private)
+ .BuildSchemaAsync()
+ .MatchSnapshotAsync();
+ }
+
[Fact]
public async Task QueryFields_ApplyDefaults_False()
{
@@ -107,6 +125,18 @@ public async Task DataResolvers_ApplyDefaults_DifferentDefaultMaxAge()
.MatchSnapshotAsync();
}
+ [Fact]
+ public async Task DataResolvers_ApplyDefaults_DifferentDefaultScope()
+ {
+ await new ServiceCollection()
+ .AddGraphQL()
+ .AddQueryType()
+ .AddCacheControl()
+ .ModifyCacheControlOptions(o => o.DefaultScope = CacheControlScope.Private)
+ .BuildSchemaAsync()
+ .MatchSnapshotAsync();
+ }
+
[Fact]
public async Task DataResolvers_ApplyDefaults_False()
{
diff --git a/src/HotChocolate/Caching/test/Caching.Tests/HttpCachingTests.cs b/src/HotChocolate/Caching/test/Caching.Tests/HttpCachingTests.cs
index 9d011736be1..ff3500101b0 100644
--- a/src/HotChocolate/Caching/test/Caching.Tests/HttpCachingTests.cs
+++ b/src/HotChocolate/Caching/test/Caching.Tests/HttpCachingTests.cs
@@ -60,6 +60,89 @@ public async Task MaxAge_Zero_Should_Cache()
result.MatchSnapshot();
}
+ [Fact]
+ public async Task Just_Defaults_Should_Cache()
+ {
+ var server = CreateServer(services =>
+ {
+ services.AddGraphQLServer()
+ .UseQueryCachePipeline()
+ .AddCacheControl()
+ .AddQueryType(d =>
+ d.Name("Query")
+ .Field("field")
+ .Resolve(""));
+ });
+
+ var client = server.CreateClient();
+ var result = await client.PostQueryAsync("{ field }");
+
+ result.MatchSnapshot();
+ }
+
+ [Fact]
+ public async Task No_Applied_Defaults_Should_Not_Cache()
+ {
+ var server = CreateServer(services =>
+ {
+ services.AddGraphQLServer()
+ .UseQueryCachePipeline()
+ .AddCacheControl()
+ .ModifyCacheControlOptions(o => o.ApplyDefaults = false)
+ .AddQueryType(d =>
+ d.Name("Query")
+ .Field("field")
+ .Resolve(""));
+ });
+
+ var client = server.CreateClient();
+ var result = await client.PostQueryAsync("{ field }");
+
+ result.MatchSnapshot();
+ }
+
+ [Fact]
+ public async Task Default_Max_Age_Should_Apply_And_Cache()
+ {
+ var server = CreateServer(services =>
+ {
+ services.AddGraphQLServer()
+ .UseQueryCachePipeline()
+ .AddCacheControl()
+ .ModifyCacheControlOptions(o => o.DefaultMaxAge = 1000)
+ .AddQueryType(d =>
+ d.Name("Query")
+ .Field("field")
+ .Resolve(""));
+ });
+
+ var client = server.CreateClient();
+ var result = await client.PostQueryAsync("{ field }");
+
+ result.MatchSnapshot();
+ }
+
+ [Fact]
+ public async Task Default_Scope_Should_Apply_And_Cache()
+ {
+ var server = CreateServer(services =>
+ {
+ services.AddGraphQLServer()
+ .UseQueryCachePipeline()
+ .AddCacheControl()
+ .ModifyCacheControlOptions(o => o.DefaultScope = CacheControlScope.Private)
+ .AddQueryType(d =>
+ d.Name("Query")
+ .Field("field")
+ .Resolve(""));
+ });
+
+ var client = server.CreateClient();
+ var result = await client.PostQueryAsync("{ field }");
+
+ result.MatchSnapshot();
+ }
+
[Fact]
public async Task JustScope_Should_Not_Cache()
{
diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.DataResolvers_ApplyDefaults_DifferentDefaultScope.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.DataResolvers_ApplyDefaults_DifferentDefaultScope.snap
new file mode 100644
index 00000000000..38b3984176e
--- /dev/null
+++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.DataResolvers_ApplyDefaults_DifferentDefaultScope.snap
@@ -0,0 +1,126 @@
+schema {
+ query: Query
+}
+
+"Information about the offset pagination."
+type CollectionSegmentInfo {
+ "Indicates whether more items exist following the set defined by the clients arguments."
+ hasNextPage: Boolean!
+ "Indicates whether more items exist prior the set defined by the clients arguments."
+ hasPreviousPage: Boolean!
+}
+
+type NestedType {
+ taskField: String! @cacheControl(maxAge: 0, scope: PRIVATE)
+ valueTaskField: String! @cacheControl(maxAge: 0, scope: PRIVATE)
+ executableField: [String!]! @cacheControl(maxAge: 0, scope: PRIVATE)
+ queryableField: [String!]! @cacheControl(maxAge: 0, scope: PRIVATE)
+ queryableFieldWithConnection("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): QueryableFieldWithConnectionConnection @cacheControl(maxAge: 0, scope: PRIVATE)
+ queryableFieldWithCollectionSegment(skip: Int take: Int): QueryableFieldWithCollectionSegmentCollectionSegment @cacheControl(maxAge: 0, scope: PRIVATE)
+ taskFieldWithCacheControl: String! @cacheControl(maxAge: 200)
+ valueTaskFieldWithCacheControl: String! @cacheControl(maxAge: 200)
+ executableFieldWithCacheControl: [String!]! @cacheControl(maxAge: 200)
+ queryableFieldWithCacheControl: [String!]! @cacheControl(maxAge: 200)
+ queryableFieldWithConnectionWithCacheControl("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): QueryableFieldWithConnectionWithCacheControlConnection @cacheControl(maxAge: 200)
+ queryableFieldWithCollectionSegmentWithCacheControl(skip: Int take: Int): QueryableFieldWithCollectionSegmentWithCacheControlCollectionSegment @cacheControl(maxAge: 200)
+ pureField: String!
+ pureFieldWithCacheControl: String! @cacheControl(maxAge: 200)
+}
+
+type NestedType2 {
+ taskFieldWithInheritMaxAge: String! @cacheControl(inheritMaxAge: true)
+}
+
+"Information about pagination in a connection."
+type PageInfo {
+ "Indicates whether more edges exist following the set defined by the clients arguments."
+ hasNextPage: Boolean!
+ "Indicates whether more edges exist prior the set defined by the clients arguments."
+ hasPreviousPage: Boolean!
+ "When paginating backwards, the cursor to continue."
+ startCursor: String
+ "When paginating forwards, the cursor to continue."
+ endCursor: String
+}
+
+type Query {
+ taskField: String! @cacheControl(maxAge: 0, scope: PRIVATE)
+ valueTaskField: String! @cacheControl(maxAge: 0, scope: PRIVATE)
+ executableField: [String!]! @cacheControl(maxAge: 0, scope: PRIVATE)
+ queryableField: [String!]! @cacheControl(maxAge: 0, scope: PRIVATE)
+ queryableFieldWithConnection("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): QueryableFieldWithConnectionConnection @cacheControl(maxAge: 0, scope: PRIVATE)
+ queryableFieldWithCollectionSegment(skip: Int take: Int): QueryableFieldWithCollectionSegmentCollectionSegment @cacheControl(maxAge: 0, scope: PRIVATE)
+ taskFieldWithCacheControl: String! @cacheControl(maxAge: 200)
+ valueTaskFieldWithCacheControl: String! @cacheControl(maxAge: 200)
+ executableFieldWithCacheControl: [String!]! @cacheControl(maxAge: 200)
+ queryableFieldWithCacheControl: [String!]! @cacheControl(maxAge: 200)
+ queryableFieldWithConnectionWithCacheControl("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): QueryableFieldWithConnectionWithCacheControlConnection @cacheControl(maxAge: 200)
+ queryableFieldWithCollectionSegmentWithCacheControl(skip: Int take: Int): QueryableFieldWithCollectionSegmentWithCacheControlCollectionSegment @cacheControl(maxAge: 200)
+ nested: NestedType! @cacheControl(maxAge: 0, scope: PRIVATE)
+ nested2: NestedType2! @cacheControl(maxAge: 0, scope: PRIVATE)
+ pureField: String! @cacheControl(maxAge: 0, scope: PRIVATE)
+ pureFieldWithCacheControl: String! @cacheControl(maxAge: 200)
+}
+
+"A segment of a collection."
+type QueryableFieldWithCollectionSegmentCollectionSegment {
+ "Information to aid in pagination."
+ pageInfo: CollectionSegmentInfo!
+ "A flattened list of the items."
+ items: [String!]
+}
+
+"A segment of a collection."
+type QueryableFieldWithCollectionSegmentWithCacheControlCollectionSegment {
+ "Information to aid in pagination."
+ pageInfo: CollectionSegmentInfo!
+ "A flattened list of the items."
+ items: [String!]
+}
+
+"A connection to a list of items."
+type QueryableFieldWithConnectionConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [QueryableFieldWithConnectionEdge!]
+ "A flattened list of the nodes."
+ nodes: [String!]
+}
+
+"An edge in a connection."
+type QueryableFieldWithConnectionEdge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: String!
+}
+
+"A connection to a list of items."
+type QueryableFieldWithConnectionWithCacheControlConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [QueryableFieldWithConnectionWithCacheControlEdge!]
+ "A flattened list of the nodes."
+ nodes: [String!]
+}
+
+"An edge in a connection."
+type QueryableFieldWithConnectionWithCacheControlEdge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: String!
+}
+
+"The scope of a cache hint."
+enum CacheControlScope {
+ "The value to cache is not tied to a single user."
+ PUBLIC
+ "The value to cache is specific to a single user."
+ PRIVATE
+}
+
+"The `@cacheControl` directive may be provided for individual fields or entire object, interface or union types to provide caching hints to the executor."
+directive @cacheControl("The maximum amount of time this field's cached value is valid, in seconds." maxAge: Int "If `PRIVATE`, the field's value is specific to a single user. The default value is `PUBLIC`, which means the field's value is not tied to a single user." scope: CacheControlScope "If `true`, the field inherits the `maxAge` of its parent field." inheritMaxAge: Boolean) on OBJECT | FIELD_DEFINITION | INTERFACE | UNION
diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.InheritMaxAgeOnQueryTypeField.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.InheritMaxAgeOnQueryTypeField.snap
index 1d68183a810..a0db67a45b2 100644
--- a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.InheritMaxAgeOnQueryTypeField.snap
+++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.InheritMaxAgeOnQueryTypeField.snap
@@ -4,9 +4,3 @@
"extensions": {}
}
-{
- "message": "Can not specify `inheritMaxAge: true` and a value for `maxAge` for the @cacheControl directive on the field Query.field.",
- "type": "Query",
- "extensions": {}
-}
-
diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.QueryFields_ApplyDefaults_DifferentDefaultScope.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.QueryFields_ApplyDefaults_DifferentDefaultScope.snap
new file mode 100644
index 00000000000..7d993f37d18
--- /dev/null
+++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/CacheControlTypeInterceptorTests.QueryFields_ApplyDefaults_DifferentDefaultScope.snap
@@ -0,0 +1,19 @@
+schema {
+ query: Query
+}
+
+type Query {
+ field1: String @cacheControl(maxAge: 0, scope: PRIVATE)
+ field2: String @cacheControl(maxAge: 200, scope: PUBLIC)
+}
+
+"The scope of a cache hint."
+enum CacheControlScope {
+ "The value to cache is not tied to a single user."
+ PUBLIC
+ "The value to cache is specific to a single user."
+ PRIVATE
+}
+
+"The `@cacheControl` directive may be provided for individual fields or entire object, interface or union types to provide caching hints to the executor."
+directive @cacheControl("The maximum amount of time this field's cached value is valid, in seconds." maxAge: Int "If `PRIVATE`, the field's value is specific to a single user. The default value is `PUBLIC`, which means the field's value is not tied to a single user." scope: CacheControlScope "If `true`, the field inherits the `maxAge` of its parent field." inheritMaxAge: Boolean) on OBJECT | FIELD_DEFINITION | INTERFACE | UNION
diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.Default_Max_Age_Should_Apply_And_Cache.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.Default_Max_Age_Should_Apply_And_Cache.snap
new file mode 100644
index 00000000000..d21e9c75222
--- /dev/null
+++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.Default_Max_Age_Should_Apply_And_Cache.snap
@@ -0,0 +1,19 @@
+{
+ "Headers": [
+ {
+ "Key": "Cache-Control",
+ "Value": [
+ "public, max-age=1000"
+ ]
+ }
+ ],
+ "ContentHeaders": [
+ {
+ "Key": "Content-Type",
+ "Value": [
+ "application/graphql-response+json; charset=utf-8"
+ ]
+ }
+ ],
+ "Body": "{}"
+}
\ No newline at end of file
diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.Default_Scope_Should_Apply_And_Cache.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.Default_Scope_Should_Apply_And_Cache.snap
new file mode 100644
index 00000000000..d0d552ebc17
--- /dev/null
+++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.Default_Scope_Should_Apply_And_Cache.snap
@@ -0,0 +1,19 @@
+{
+ "Headers": [
+ {
+ "Key": "Cache-Control",
+ "Value": [
+ "max-age=0, private"
+ ]
+ }
+ ],
+ "ContentHeaders": [
+ {
+ "Key": "Content-Type",
+ "Value": [
+ "application/graphql-response+json; charset=utf-8"
+ ]
+ }
+ ],
+ "Body": "{}"
+}
\ No newline at end of file
diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.Just_Defaults_Should_Cache.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.Just_Defaults_Should_Cache.snap
new file mode 100644
index 00000000000..0a3c8d90948
--- /dev/null
+++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.Just_Defaults_Should_Cache.snap
@@ -0,0 +1,19 @@
+{
+ "Headers": [
+ {
+ "Key": "Cache-Control",
+ "Value": [
+ "public, max-age=0"
+ ]
+ }
+ ],
+ "ContentHeaders": [
+ {
+ "Key": "Content-Type",
+ "Value": [
+ "application/graphql-response+json; charset=utf-8"
+ ]
+ }
+ ],
+ "Body": "{}"
+}
\ No newline at end of file
diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.No_Applied_Defaults_Should_Not_Cache.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.No_Applied_Defaults_Should_Not_Cache.snap
new file mode 100644
index 00000000000..140ac8d2711
--- /dev/null
+++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.No_Applied_Defaults_Should_Not_Cache.snap
@@ -0,0 +1,12 @@
+{
+ "Headers": [],
+ "ContentHeaders": [
+ {
+ "Key": "Content-Type",
+ "Value": [
+ "application/graphql-response+json; charset=utf-8"
+ ]
+ }
+ ],
+ "Body": "{\"data\":{\"field\":\"\"}}"
+}
\ No newline at end of file
diff --git a/src/HotChocolate/Core/src/Types/Types/Argument.cs b/src/HotChocolate/Core/src/Types/Types/Argument.cs
index 960d1676cfc..ef59601657f 100644
--- a/src/HotChocolate/Core/src/Types/Types/Argument.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Argument.cs
@@ -82,7 +82,7 @@ protected sealed override void OnCompleteField(
ITypeSystemMember declaringMember,
FieldDefinitionBase definition)
=> OnCompleteField(context, declaringMember, (ArgumentDefinition)definition);
-
+
protected virtual void OnCompleteField(
ITypeCompletionContext context,
ITypeSystemMember declaringMember,
@@ -94,7 +94,7 @@ protected virtual void OnCompleteField(
.SetMessage(TypeResources.Argument_TypeIsNull, definition.Name)
.SetTypeSystemObject(context.Type)
.SetExtension("declaringMember", declaringMember)
- .SetExtension("name", definition.Name.ToString())
+ .SetExtension("name", definition.Name)
.Build());
return;
}
@@ -102,7 +102,7 @@ protected virtual void OnCompleteField(
base.OnCompleteField(context, declaringMember, definition);
Type = context.GetType(definition.Type!).EnsureInputType();
- _runtimeType = definition.RuntimeType ?? definition.Parameter?.ParameterType!;
+ _runtimeType = definition.GetRuntimeType()!;
_runtimeType = CompleteRuntimeType(Type, _runtimeType, out var isOptional);
DefaultValue = CompleteDefaultValue(context, definition, Type, Coordinate);
IsOptional = isOptional;
diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/ArgumentDefinition.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/ArgumentDefinition.cs
index 0db6692e639..db6d0f1ad71 100644
--- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/ArgumentDefinition.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/ArgumentDefinition.cs
@@ -48,6 +48,8 @@ public ArgumentDefinition(
public IList Formatters =>
_formatters ??= [];
+ public virtual Type? GetRuntimeType() => RuntimeType ?? Parameter?.ParameterType;
+
public IReadOnlyList GetFormatters()
{
if (_formatters is null)
diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/DirectiveArgumentDefinition.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/DirectiveArgumentDefinition.cs
index 5add37e6681..b821d693953 100644
--- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/DirectiveArgumentDefinition.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Definitions/DirectiveArgumentDefinition.cs
@@ -1,3 +1,4 @@
+using System;
using System.Reflection;
using HotChocolate.Language;
using HotChocolate.Utilities;
@@ -37,4 +38,6 @@ public DirectiveArgumentDefinition(
/// The property to which this argument binds to.
///
public PropertyInfo? Property { get; set; }
+
+ public override Type? GetRuntimeType() => RuntimeType ?? Property?.PropertyType;
}
diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Directives/TagDirectiveTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Directives/TagDirectiveTests.cs
index 277ee776a18..c3ee4841bc2 100644
--- a/src/HotChocolate/Core/test/Types.Tests/Types/Directives/TagDirectiveTests.cs
+++ b/src/HotChocolate/Core/test/Types.Tests/Types/Directives/TagDirectiveTests.cs
@@ -21,7 +21,7 @@ public async Task EnsureAllLocationsAreApplied()
schema.MatchSnapshot();
}
-
+
[Fact]
public async Task SchemaFirst_Tag()
{
@@ -33,10 +33,10 @@ public async Task SchemaFirst_Tag()
type Query {
field: String @tag(name: "abc")
}
-
- directive @tag("The name of the tag." name: String!)
- repeatable on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION |
- ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE |
+
+ directive @tag("The name of the tag." name: String!)
+ repeatable on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION |
+ ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE |
INPUT_OBJECT | INPUT_FIELD_DEFINITION
""")
.UseField(_ => _ => default)