Skip to content

Commit c4a505e

Browse files
authored
Updated tag name validation to allow hyphens and relax requirements (#8304)
1 parent 38bf917 commit c4a505e

File tree

4 files changed

+68
-69
lines changed

4 files changed

+68
-69
lines changed

src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/HotChocolate/Core/src/Types/Properties/TypeResources.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,7 @@ Type: `{0}`</value>
965965
<value>The specified type `{0}` is expected to be an output type.</value>
966966
</data>
967967
<data name="TagDirective_Name_NotValid" xml:space="preserve">
968-
<value>The tag name must follow the GraphQL type name rules.</value>
968+
<value>Tag names may only include alphanumeric characters (a-z, A-Z, 0-9), hyphens, and underscores.</value>
969969
</data>
970970
<data name="TagDirective_Descriptor_NotSupported" xml:space="preserve">
971971
<value>Tag is not supported on the specified descriptor.</value>
Lines changed: 7 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#nullable enable
22

3-
using System.Runtime.CompilerServices;
3+
using System.Text.RegularExpressions;
44
using HotChocolate.Properties;
55

66
namespace HotChocolate.Types;
@@ -47,7 +47,7 @@ interface Book {
4747
author: String!
4848
}
4949
""")]
50-
public sealed class Tag
50+
public sealed partial class Tag
5151
{
5252
/// <summary>
5353
/// Creates a new instance of <see cref="Tag"/>.
@@ -60,7 +60,9 @@ public sealed class Tag
6060
/// </exception>
6161
public Tag(string name)
6262
{
63-
if (!IsValidTagName(name))
63+
ArgumentNullException.ThrowIfNull(name);
64+
65+
if (!ValidNameRegex().IsMatch(name))
6466
{
6567
throw new ArgumentException(
6668
TypeResources.TagDirective_Name_NotValid,
@@ -77,68 +79,6 @@ public Tag(string name)
7779
[GraphQLDescription("The name of the tag.")]
7880
public string Name { get; }
7981

80-
private static bool IsValidTagName(string? name)
81-
{
82-
if (string.IsNullOrEmpty(name))
83-
{
84-
return false;
85-
}
86-
87-
var span = name.AsSpan();
88-
89-
if (IsLetterOrUnderscore(span[0]))
90-
{
91-
if (span.Length > 1)
92-
{
93-
for (var i = 1; i < span.Length; i++)
94-
{
95-
if (!IsLetterOrDigitOrUnderscoreOrHyphen(span[i]))
96-
{
97-
return false;
98-
}
99-
}
100-
}
101-
102-
return true;
103-
}
104-
105-
return false;
106-
}
107-
108-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
109-
private static bool IsLetterOrDigitOrUnderscoreOrHyphen(char c)
110-
{
111-
if (c is > (char)96 and < (char)123 or > (char)64 and < (char)91)
112-
{
113-
return true;
114-
}
115-
116-
if (c is > (char)47 and < (char)58)
117-
{
118-
return true;
119-
}
120-
121-
if (c == '_' || c == '-')
122-
{
123-
return true;
124-
}
125-
126-
return false;
127-
}
128-
129-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
130-
private static bool IsLetterOrUnderscore(char c)
131-
{
132-
if (c is > (char)96 and < (char)123 or > (char)64 and < (char)91)
133-
{
134-
return true;
135-
}
136-
137-
if (c == '_')
138-
{
139-
return true;
140-
}
141-
142-
return false;
143-
}
82+
[GeneratedRegex("^[a-zA-Z0-9_-]+$")]
83+
private static partial Regex ValidNameRegex();
14484
}

src/HotChocolate/Core/test/Types.Tests/Types/Directives/TagDirectiveTests.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,65 @@ repeatable on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION |
4343
schema.MatchSnapshot();
4444
}
4545

46+
[Fact]
47+
public async Task ValidNames()
48+
{
49+
var exception = await Record.ExceptionAsync(
50+
async () => await new ServiceCollection()
51+
.AddGraphQL()
52+
.AddDocumentFromString(
53+
"""
54+
type Query {
55+
field: String
56+
@tag(name: "tag")
57+
@tag(name: "TAG")
58+
@tag(name: "TAG_123")
59+
@tag(name: "tag_123")
60+
@tag(name: "tag-123")
61+
}
62+
63+
directive @tag("The name of the tag." name: String!)
64+
repeatable on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION |
65+
ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE |
66+
INPUT_OBJECT | INPUT_FIELD_DEFINITION
67+
""")
68+
.UseField(_ => _ => default)
69+
.BuildSchemaAsync());
70+
71+
Assert.Null(exception);
72+
}
73+
74+
[Theory]
75+
[InlineData("tag name")]
76+
[InlineData("tag*")]
77+
[InlineData("@TAG")]
78+
[InlineData("tag=name")]
79+
[InlineData("tagK")] // K = Kelvin Sign (U+212A)
80+
public async Task InvalidNames(string tagName)
81+
{
82+
async Task Act() =>
83+
await new ServiceCollection()
84+
.AddGraphQL()
85+
.AddDocumentFromString(
86+
$$"""
87+
type Query {
88+
field: String @tag(name: "{{tagName}}")
89+
}
90+
91+
directive @tag("The name of the tag." name: String!)
92+
repeatable on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION |
93+
ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE |
94+
INPUT_OBJECT | INPUT_FIELD_DEFINITION
95+
""")
96+
.UseField(_ => _ => default)
97+
.BuildSchemaAsync();
98+
99+
Assert.Equal(
100+
"Tag names may only include alphanumeric characters (a-z, A-Z, 0-9), hyphens, and " +
101+
"underscores. (Parameter 'name')",
102+
(await Assert.ThrowsAsync<ArgumentException>(Act)).Message);
103+
}
104+
46105
[Tag("OnObjectType")]
47106
public class Query
48107
{

0 commit comments

Comments
 (0)