diff --git a/src/HotChocolate/Core/src/Abstractions/NameFormattingHelpers.cs b/src/HotChocolate/Core/src/Abstractions/NameFormattingHelpers.cs index 929dbfd7760..830103b72c9 100644 --- a/src/HotChocolate/Core/src/Abstractions/NameFormattingHelpers.cs +++ b/src/HotChocolate/Core/src/Abstractions/NameFormattingHelpers.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text; using System.Threading.Tasks; using HotChocolate.Properties; using HotChocolate.Utilities; @@ -17,7 +18,7 @@ internal static class NameFormattingHelpers { private const string _get = "Get"; private const string _async = "Async"; - private const string _typePostfix = "`1"; + private const char _genericTypeDelimiter = '`'; public static string GetGraphQLName(this Type type) { @@ -26,10 +27,7 @@ public static string GetGraphQLName(this Type type) throw new ArgumentNullException(nameof(type)); } - var typeInfo = type.GetTypeInfo(); - var name = typeInfo.IsDefined(typeof(GraphQLNameAttribute), false) - ? typeInfo.GetCustomAttribute()!.Name - : GetFromType(typeInfo); + var name = GetFromType(type); return NameUtils.MakeValidGraphQLName(name)!; } @@ -184,21 +182,44 @@ public static bool IsDeprecated( private static string GetFromType(Type type) { - if (type.GetTypeInfo().IsGenericType) + var typeName = type.IsDefined(typeof(GraphQLNameAttribute), false) + ? type.GetCustomAttribute()!.Name + : null; + + if (type.IsGenericType) { - var name = type.GetTypeInfo() - .GetGenericTypeDefinition() - .Name; + if (typeName == null) + { + typeName = type.GetGenericTypeDefinition().Name; + + var nameSpan = typeName.AsSpan(); + var index = nameSpan.LastIndexOf(_genericTypeDelimiter); + + if (index >= 0) + { + nameSpan = nameSpan.Slice(0, index); + } + + typeName = nameSpan.ToString(); + } - name = name.Substring(0, name.Length - _typePostfix.Length); + var arguments = type.GetGenericArguments(); + var stringBuilder = new StringBuilder(typeName).Append("Of"); - var arguments = type - .GetTypeInfo().GenericTypeArguments - .Select(GetFromType); + for (var i = 0; i < arguments.Length; i++) + { + if (i > 0) + { + stringBuilder.Append("And"); + } - return $"{name}Of{string.Join("And", arguments)}"; + stringBuilder.Append(GetFromType(arguments[i])); + } + + return stringBuilder.ToString(); } - return type.Name; + + return typeName ?? type.Name; } public static unsafe string FormatFieldName(string fieldName) diff --git a/src/HotChocolate/Core/test/Types.Tests/GenericTypeNamingTests.cs b/src/HotChocolate/Core/test/Types.Tests/GenericTypeNamingTests.cs new file mode 100644 index 00000000000..d161de6618d --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/GenericTypeNamingTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Threading.Tasks; +using HotChocolate.Execution; +using HotChocolate.Tests; +using Microsoft.Extensions.DependencyInjection; + +#nullable enable + +namespace HotChocolate; + +public class GenericTypesNamingTests +{ + [Fact] + public async Task NamingResolution() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + + public class Query + { + public Tuple OneGenericType => default!; + public Tuple TwoGenericsType => default!; + public Tuple ThreeGenericsType => default!; + public Tuple FourGenericsType => default!; + public Tuple FiveGenericTypes => default!; + public Tuple SixGenericTypes => default!; + public Tuple SevenGenericTypes => default!; + public EightElementsTuple EightGenericTypes => default!; + public NineElementsTuple NineGenericTypes => default!; + public TenElementsTuple TenGenericTypes => default!; + public Foo IntBar => default!; + public Foo StringBar => default!; + public Foo CustomNameBar => default!; + } + + public class EightElementsTuple : Tuple + { + public T8 Item8 { get; set; } = default!; + + public EightElementsTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8) + : base(item1, item2, item3, item4, item5, item6, item7) + { + Item8 = item8; + } + } + + public class NineElementsTuple : EightElementsTuple + { + public T9 Item9 { get; set; } = default!; + + public NineElementsTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9) + : base(item1, item2, item3, item4, item5, item6, item7, item8) + { + Item9 = item9; + } + } + + public class TenElementsTuple : NineElementsTuple + { + public T10 Item10 { get; set; } = default!; + + public TenElementsTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9, T10 item10) + : base(item1, item2, item3, item4, item5, item6, item7, item8, item9) + { + Item10 = item10; + } + } + + [GraphQLName("Bar")] + public class Foo + { + public T Test { get; init; } + } + + [GraphQLName("MyType")] + public class Bar + { + public int Test { get; init; } + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/GenericTypesNamingTests.NamingResolution.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/GenericTypesNamingTests.NamingResolution.snap new file mode 100644 index 00000000000..620c222ac61 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/GenericTypesNamingTests.NamingResolution.snap @@ -0,0 +1,120 @@ +schema { + query: Query +} + +type BarOfInt32 { + test: Int! +} + +type BarOfMyType { + test: MyType! +} + +type BarOfString { + test: String! +} + +type EightElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32 { + item8: Int! + item1: Int! + item2: Int! + item3: Int! + item4: Int! + item5: Int! + item6: Int! + item7: Int! +} + +type MyType { + test: Int! +} + +type NineElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32 { + item9: Int! + item8: Int! + item1: Int! + item2: Int! + item3: Int! + item4: Int! + item5: Int! + item6: Int! + item7: Int! +} + +type Query { + oneGenericType: TupleOfInt32! + twoGenericsType: TupleOfInt32AndInt32! + threeGenericsType: TupleOfInt32AndInt32AndInt32! + fourGenericsType: TupleOfInt32AndInt32AndInt32AndInt32! + fiveGenericTypes: TupleOfInt32AndInt32AndInt32AndInt32AndInt32! + sixGenericTypes: TupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32! + sevenGenericTypes: TupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32! + eightGenericTypes: EightElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32! + nineGenericTypes: NineElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32! + tenGenericTypes: TenElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32! + intBar: BarOfInt32! + stringBar: BarOfString! + customNameBar: BarOfMyType! +} + +type TenElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32 { + item10: Int! + item9: Int! + item8: Int! + item1: Int! + item2: Int! + item3: Int! + item4: Int! + item5: Int! + item6: Int! + item7: Int! +} + +type TupleOfInt32 { + item1: Int! +} + +type TupleOfInt32AndInt32 { + item1: Int! + item2: Int! +} + +type TupleOfInt32AndInt32AndInt32 { + item1: Int! + item2: Int! + item3: Int! +} + +type TupleOfInt32AndInt32AndInt32AndInt32 { + item1: Int! + item2: Int! + item3: Int! + item4: Int! +} + +type TupleOfInt32AndInt32AndInt32AndInt32AndInt32 { + item1: Int! + item2: Int! + item3: Int! + item4: Int! + item5: Int! +} + +type TupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32 { + item1: Int! + item2: Int! + item3: Int! + item4: Int! + item5: Int! + item6: Int! +} + +type TupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32 { + item1: Int! + item2: Int! + item3: Int! + item4: Int! + item5: Int! + item6: Int! + item7: Int! +}