Skip to content

Use correct type name when referencing type with ID<Type> #8504

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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 @@ -170,31 +170,8 @@ public class IDAttribute<T> : DescriptorAttribute
/// <inheritdoc cref="IDAttribute{T}"/>
public IDAttribute()
{
TypeName = typeof(T).Name;
}

/// <summary>
/// With the <see cref="IDAttribute.TypeName"/> property you can override the type name
/// of the ID. This is useful to rewrite a parameter of a mutation or query, to a specific
/// id.
/// </summary>
/// <example>
/// <para>
/// A field can be rewritten to an ID by adding <c>[ID]</c> to the resolver.
/// </para>
/// <code>
/// public class UserQuery
/// {
/// public User GetUserById([ID("User")] int id) => //....
/// }
/// </code>
/// <para>
/// The argument is rewritten to <c>ID</c> and expect an ID of type User.
/// Assuming `<c>User.id</c>` has the value 1. The following string is base64 encoded
/// </para>
/// </example>
public string? TypeName { get; }

/// <inheritdoc />
protected internal override void TryConfigure(
IDescriptorContext context,
Expand All @@ -204,13 +181,13 @@ protected internal override void TryConfigure(
switch (descriptor)
{
case IInputFieldDescriptor d when element is PropertyInfo:
d.ID(TypeName);
d.ID<T>();
break;
case IArgumentDescriptor d when element is ParameterInfo:
d.ID(TypeName);
d.ID<T>();
break;
case IObjectFieldDescriptor d when element is MemberInfo:
d.ID(TypeName);
d.ID<T>();
break;
case IInterfaceFieldDescriptor d when element is MemberInfo:
d.ID();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#nullable enable
namespace HotChocolate.Types.Relay;

/// <summary>
/// A discriminated union, containing either a literal or a type that defines
/// the name of the node identifier.
/// </summary>
internal record NodeIdNameDefinitionUnion(string? Literal, Type? Type)
{
public static NodeIdNameDefinitionUnion? Create(string? literal) =>
literal == null ? null : new NodeIdNameDefinitionUnion(literal, null);

public static NodeIdNameDefinitionUnion Create<T>() =>
new NodeIdNameDefinitionUnion(null, typeof(T));
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,15 @@ public static IInputFieldDescriptor ID(
return descriptor;
}

/// <inheritdoc cref="RelayIdFieldExtensions"/>
/// <param name="descriptor">the descriptor</param>
/// <inheritdoc cref="ID(IInputFieldDescriptor,string?)"/>
/// <typeparam name="T">
/// the type from which the <see cref="IDAttribute.TypeName">type name</see> is derived
/// </typeparam>
public static IInputFieldDescriptor ID<T>(this IInputFieldDescriptor descriptor)
{
ArgumentNullException.ThrowIfNull(descriptor);

RelayIdFieldHelpers.ApplyIdToField(descriptor, typeof(T).Name);
RelayIdFieldHelpers.ApplyIdToField<T>(descriptor);

return descriptor;
}
Expand All @@ -106,16 +105,15 @@ public static IArgumentDescriptor ID(
return descriptor;
}

/// <inheritdoc cref="RelayIdFieldExtensions"/>
/// <param name="descriptor">the descriptor</param>
/// <inheritdoc cref="ID(IInputFieldDescriptor,string?)"/>
/// <typeparam name="T">
/// the type from which the <see cref="IDAttribute.TypeName">type name</see> is derived
/// </typeparam>
public static IArgumentDescriptor ID<T>(this IArgumentDescriptor descriptor)
{
ArgumentNullException.ThrowIfNull(descriptor);

RelayIdFieldHelpers.ApplyIdToField(descriptor, typeof(T).Name);
RelayIdFieldHelpers.ApplyIdToField<T>(descriptor);

return descriptor;
}
Expand All @@ -136,16 +134,15 @@ public static IObjectFieldDescriptor ID(
return descriptor;
}

/// <inheritdoc cref="RelayIdFieldExtensions"/>
/// <param name="descriptor">the descriptor</param>
/// <inheritdoc cref="ID(IInputFieldDescriptor,string?)"/>
/// <typeparam name="T">
/// the type from which the <see cref="IDAttribute.TypeName">type name</see> is derived
/// </typeparam>
public static IObjectFieldDescriptor ID<T>(this IObjectFieldDescriptor descriptor)
{
ArgumentNullException.ThrowIfNull(descriptor);

RelayIdFieldHelpers.ApplyIdToField(descriptor, typeof(T).Name);
RelayIdFieldHelpers.ApplyIdToField<T>(descriptor);

return descriptor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,16 @@ internal static class RelayIdFieldHelpers
/// </remarks>
public static void ApplyIdToField(
IDescriptor<ArgumentConfiguration> descriptor,
string? typeName = null)
{
ArgumentNullException.ThrowIfNull(descriptor);

var extend = descriptor.Extend();

// rewrite type
extend.OnBeforeCreate(RewriteConfiguration);
string? typeName = null) =>
ApplyIdToFieldCore(descriptor, NodeIdNameDefinitionUnion.Create(typeName));

// add serializer if globalID support is enabled.
if (extend.Context.Features.Get<NodeSchemaFeature>()?.IsEnabled == true)
{
extend.OnBeforeCompletion((c, d) => AddSerializerToInputField(c, d, typeName));
}
}
/// <inheritdoc cref="ApplyIdToField(IDescriptor{ArgumentConfiguration},string?)"/>
/// <typeparam name="T">
/// the type from which the <see cref="IDAttribute.TypeName">type name</see> is derived
/// </typeparam>
public static void ApplyIdToField<T>(
IDescriptor<ArgumentConfiguration> descriptor) =>
ApplyIdToFieldCore(descriptor, NodeIdNameDefinitionUnion.Create<T>());

/// <summary>
/// Applies the <see cref="RelayIdFieldExtensions"><c>.ID()</c></see> to an argument
Expand All @@ -50,24 +45,16 @@ public static void ApplyIdToField(
/// </remarks>
public static void ApplyIdToField(
IDescriptor<OutputFieldConfiguration> descriptor,
string? typeName = null)
{
ArgumentNullException.ThrowIfNull(descriptor);

// rewrite type
descriptor.Extend().OnBeforeCreate(RewriteConfiguration);
string? typeName = null) =>
ApplyIdToFieldCore(descriptor, NodeIdNameDefinitionUnion.Create(typeName));

if (descriptor is IDescriptor<ObjectFieldConfiguration> objectFieldDescriptor)
{
var extend = objectFieldDescriptor.Extend();

// add serializer if globalID support is enabled.
if (extend.Context.Features.Get<NodeSchemaFeature>()?.IsEnabled == true)
{
ApplyIdToField(extend.Configuration, typeName);
}
}
}
/// <inheritdoc cref="ApplyIdToField(IDescriptor{OutputFieldConfiguration},string?)"/>
/// <typeparam name="T">
/// the type from which the <see cref="IDAttribute.TypeName">type name</see> is derived
/// </typeparam>
public static void ApplyIdToField<T>(
IDescriptor<OutputFieldConfiguration> descriptor) =>
ApplyIdToFieldCore(descriptor, NodeIdNameDefinitionUnion.Create<T>());

/// <summary>
/// Applies the <see cref="RelayIdFieldExtensions"><c>.ID()</c></see> to an argument
Expand All @@ -78,7 +65,8 @@ public static void ApplyIdToField(
/// </remarks>
internal static void ApplyIdToField(
ObjectFieldConfiguration configuration,
string? typeName = null)
NodeIdNameDefinitionUnion? nameDefinition = null,
TypeReference? dependsOn = null)
{
var placeholder = new ResultFormatterConfiguration(
(_, r) => r,
Expand All @@ -91,12 +79,75 @@ internal static void ApplyIdToField(
ctx,
(ObjectFieldConfiguration)def,
placeholder,
typeName),
nameDefinition),
configuration,
ApplyConfigurationOn.BeforeCompletion);
ApplyConfigurationOn.BeforeCompletion,
typeReference: dependsOn);

configuration.Tasks.Add(configurationTask);
}
internal static void ApplyIdToFieldCore(
IDescriptor<OutputFieldConfiguration> descriptor,
NodeIdNameDefinitionUnion? nameDefinition)
{
ArgumentNullException.ThrowIfNull(descriptor);

// rewrite type
descriptor.Extend().OnBeforeCreate(RewriteConfiguration);

if (descriptor is IDescriptor<ObjectFieldConfiguration> objectFieldDescriptor)
{
var extend = objectFieldDescriptor.Extend();

// add serializer if globalID support is enabled.
if (extend.Context.Features.Get<NodeSchemaFeature>()?.IsEnabled == true)
{
if (nameDefinition?.Type != null)
{
var dependsOn = extend.Context.TypeInspector.GetTypeRef(nameDefinition.Type);
ApplyIdToField(extend.Configuration, nameDefinition, dependsOn);
}
else
{
ApplyIdToField(extend.Configuration, nameDefinition);
}
}
}
}

public static void ApplyIdToFieldCore(
IDescriptor<ArgumentConfiguration> descriptor,
NodeIdNameDefinitionUnion? nameDefinition)
{
ArgumentNullException.ThrowIfNull(descriptor);

var extend = descriptor.Extend();

// rewrite type
extend.OnBeforeCreate(RewriteConfiguration);

// add serializer if globalID support is enabled.
if (extend.Context.Features.Get<NodeSchemaFeature>()?.IsEnabled == true)
{
if (nameDefinition?.Type == null)
{
extend.OnBeforeCompletion((c, d) =>
AddSerializerToInputField(c, d, nameDefinition));
}
else
{
var dependsOn = extend.Context.TypeInspector.GetTypeRef(nameDefinition.Type);

var configurationTask = new OnCompleteTypeSystemConfigurationTask(
(ctx, def) => AddSerializerToInputField(ctx, (ArgumentConfiguration)def, nameDefinition),
extend.Configuration,
ApplyConfigurationOn.BeforeCompletion,
typeReference: dependsOn);

extend.Configuration.Tasks.Add(configurationTask);
}
}
}

private static void RewriteConfiguration(
IDescriptorContext context,
Expand Down Expand Up @@ -139,7 +190,7 @@ private static IExtendedType RewriteType(ITypeInspector typeInspector, ITypeInfo
internal static void AddSerializerToInputField(
ITypeCompletionContext completionContext,
ArgumentConfiguration configuration,
string? typeName)
NodeIdNameDefinitionUnion? nameDefinition)
{
var typeInspector = completionContext.TypeInspector;
IExtendedType? resultType;
Expand Down Expand Up @@ -167,6 +218,8 @@ internal static void AddSerializerToInputField(
completionContext.Type);
}

var typeName = GetIdTypeName(completionContext, nameDefinition, typeInspector);

var validateType = typeName is not null;
typeName ??= completionContext.Type.Name;
SetSerializerInfos(completionContext.DescriptorContext, typeName, resultType);
Expand All @@ -178,7 +231,7 @@ private static void AddSerializerToObjectField(
ITypeCompletionContext completionContext,
ObjectFieldConfiguration configuration,
ResultFormatterConfiguration placeholder,
string? typeName)
NodeIdNameDefinitionUnion? nameDefinition)
{
var typeInspector = completionContext.TypeInspector;
IExtendedType? resultType;
Expand All @@ -201,6 +254,8 @@ private static void AddSerializerToObjectField(
var serializerAccessor = completionContext.DescriptorContext.NodeIdSerializerAccessor;
var index = configuration.FormatterConfigurations.IndexOf(placeholder);

var typeName = GetIdTypeName(completionContext, nameDefinition, typeInspector);

typeName ??= completionContext.Type.Name;
SetSerializerInfos(completionContext.DescriptorContext, typeName, resultType);

Expand Down Expand Up @@ -281,4 +336,19 @@ internal static void SetSerializerInfos(IDescriptorContext context, string typeN
var feature = context.Features.GetOrSet<NodeSchemaFeature>();
feature.NodeIdTypes.TryAdd(typeName, runtimeTypeInfo.NamedType);
}

private static string? GetIdTypeName(ITypeCompletionContext completionContext,
NodeIdNameDefinitionUnion? nameDefinition,
ITypeInspector typeInspector)
{
var typeName = nameDefinition?.Literal;
if (nameDefinition?.Type is { } t)
{
var referencedType = typeInspector.GetType(t);
var foo = completionContext.GetType<IType>(TypeReference.Create(referencedType));
typeName = foo.NamedType().Name;
}

return typeName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Metadata;
using HotChocolate.Configuration;
using HotChocolate.Features;
using HotChocolate.Language;
Expand Down Expand Up @@ -157,7 +158,7 @@ public override void OnAfterMergeTypeExtensions()
RelayIdFieldHelpers.AddSerializerToInputField(
CompletionContext,
argument,
fieldTypeDef.Name);
NodeIdNameDefinitionUnion.Create(fieldTypeDef.Name));

// As with the id argument, we also want to make sure that the ID field of
// the field result type is a non-null ID type.
Expand Down
Loading