Skip to content

Commit

Permalink
Correctly handle null for lists of nullable value-type IDs (#7933)
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-tengler authored Jan 17, 2025
1 parent 565cc9c commit d3da9b0
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ private static IInputValueFormatter CreateSerializer(
return new GlobalIdInputValueFormatter(
completionContext.DescriptorContext.NodeIdSerializerAccessor,
resultTypeInfo.NamedType,
resultType.ElementType?.Type ?? resultTypeInfo.NamedType,
resultType.ElementType?.Source ?? resultTypeInfo.NamedType,
typeName ?? completionContext.Type.Name,
validateType);
}
Expand Down
109 changes: 53 additions & 56 deletions src/HotChocolate/Core/test/Types.Tests/Types/Relay/IdAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,25 @@ query foo(
nullableIntIdGivenNull: nullableIntId(id: $nullIntId)
optionalIntId(id: $intId)
optionalIntIdGivenNothing: optionalIntId
intIdList(id: [$intId])
nullableIntIdList(id: [$intId, $nullIntId])
optionalIntIdList(id: [$intId])
intIdList(ids: [$intId])
nullableIntIdList(ids: [$intId, $nullIntId])
optionalIntIdList(ids: [$intId])
stringId(id: $stringId)
nullableStringId(id: $stringId)
nullableStringIdGivenNull: nullableStringId(id: $nullStringId)
optionalStringId(id: $stringId)
optionalStringIdGivenNothing: optionalStringId
stringIdList(id: [$stringId])
nullableStringIdList(id: [$stringId, $nullStringId])
optionalStringIdList(id: [$stringId])
stringIdList(ids: [$stringId])
nullableStringIdList(ids: [$stringId, $nullStringId])
optionalStringIdList(ids: [$stringId])
guidId(id: $guidId)
nullableGuidId(id: $guidId)
nullableGuidIdGivenNull: nullableGuidId(id: $nullGuidId)
optionalGuidId(id: $guidId)
optionalGuidIdGivenNothing: optionalGuidId
guidIdList(id: [$guidId $guidId])
nullableGuidIdList(id: [$guidId $nullGuidId $guidId])
optionalGuidIdList(id: [$guidId $guidId])
guidIdList(ids: [$guidId $guidId])
nullableGuidIdList(ids: [$guidId $nullGuidId $guidId])
optionalGuidIdList(ids: [$guidId $guidId])
customId(id: $customId)
nullableCustomId(id: $customId)
nullableCustomIdGivenNull: nullableCustomId(id: $nullCustomId)
Expand Down Expand Up @@ -106,7 +106,7 @@ public async Task InterceptedId_On_Arguments()
OperationRequestBuilder.New()
.SetDocument(@"query foo {
interceptedId(id: 1)
interceptedIds(id: [1, 2])
interceptedIds(ids: [1, 2])
}")
.Build());

Expand Down Expand Up @@ -464,56 +464,53 @@ public async Task EnsureIdIsOnlyAppliedOnce()
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public class Query
{
public string IntId([ID] int id) => id.ToString();
public string IntIdList([ID] int[] id) =>
string.Join(", ", id.Select(t => t.ToString()));
public int IntId([ID] int id) => id;

public string NullableIntId([ID] int? id) => id?.ToString() ?? "null";
public string NullableIntIdList([ID] int?[] id) =>
string.Join(", ", id.Select(t => t?.ToString() ?? "null"));
public int[] IntIdList([ID] int[] ids) => ids;

public string OptionalIntId([DefaultValue("UXVlcnk6MA==")][ID] Optional<int> id) =>
id.HasValue ? id.Value.ToString() : "NO VALUE";
public string OptionalIntIdList([DefaultValue(new int[] {})][ID] Optional<int[]> id) =>
id.HasValue ? string.Join(", ", id.Value.Select(t => t.ToString())) : "NO VALUE";
public int? NullableIntId([ID] int? id) => id;

public int?[] NullableIntIdList([ID] int?[] ids) => ids;

public int? OptionalIntId([DefaultValue("UXVlcnk6MA==")][ID] Optional<int> id)
=> id.HasValue ? id.Value : null;

public int[]? OptionalIntIdList([DefaultValue(new int[] {})][ID] Optional<int[]> ids)
=> ids.HasValue ? ids.Value : null;

public string StringId([ID] string id) => id;
public string StringIdList([ID] string[] id) =>
string.Join(", ", id.Select(t => t.ToString()));

public string NullableStringId([ID] string? id) => id ?? "null";
public string NullableStringIdList([ID] string?[] id) =>
string.Join(", ", id.Select(t => t?.ToString() ?? "null"));

public string OptionalStringId(
[DefaultValue("UXVlcnk6")][ID] Optional<string> id) =>
id.HasValue ? id.Value : "NO VALUE";
public string OptionalStringIdList(
[DefaultValue(new string[] {})][ID] Optional<string[]> id) =>
id.HasValue ? string.Join(", ", id.Value) : "NO VALUE";

public string GuidId([ID] Guid id) => id.ToString();
public string GuidIdList([ID] IReadOnlyList<Guid> id) =>
string.Join(", ", id.Select(t => t.ToString()));

public string NullableGuidId([ID] Guid? id) => id?.ToString() ?? "null";
public string NullableGuidIdList([ID] IReadOnlyList<Guid?> id) =>
string.Join(", ", id.Select(t => t?.ToString() ?? "null"));

public string OptionalGuidId(
[DefaultValue("UXVlcnk6AAAAAAAAAAAAAAAAAAAAAA==")][ID] Optional<Guid> id) =>
id.HasValue ? id.Value.ToString() : "NO VALUE";
public string OptionalGuidIdList(
[DefaultValue(new object[] {})][ID] Optional<Guid[]> id) =>
id.HasValue ? string.Join(", ", id.Value.Select(t => t.ToString())) : "NO VALUE";

public string InterceptedId([InterceptedID("Query")] [ID] int id) => id.ToString();

public string InterceptedIds([InterceptedID("Query")] [ID] int[] id) =>
string.Join(", ", id.Select(t => t.ToString()));

public string CustomId([ID] StronglyTypedId id) =>
id.ToString();

public string[] StringIdList([ID] string[] ids) => ids;

public string? NullableStringId([ID] string? id) => id;

public string?[] NullableStringIdList([ID] string?[] ids) => ids;

public string? OptionalStringId([DefaultValue("UXVlcnk6")][ID] Optional<string> id)
=> id.HasValue ? id.Value : null;

public string[]? OptionalStringIdList([DefaultValue(new string[] { })] [ID] Optional<string[]> ids)
=> ids.HasValue ? ids.Value : null;

public Guid GuidId([ID] Guid id) => id;

public IReadOnlyList<Guid> GuidIdList([ID] IReadOnlyList<Guid> ids) => ids;

public Guid? NullableGuidId([ID] Guid? id) => id;

public IReadOnlyList<Guid?> NullableGuidIdList([ID] IReadOnlyList<Guid?> ids) => ids;

public Guid? OptionalGuidId([DefaultValue("UXVlcnk6AAAAAAAAAAAAAAAAAAAAAA==")][ID] Optional<Guid> id)
=> id.HasValue ? id.Value : null;

public Guid[]? OptionalGuidIdList([DefaultValue(new object[] { })] [ID] Optional<Guid[]> ids)
=> ids.HasValue ? ids.Value : null;

public int InterceptedId([InterceptedID("Query")] [ID] int id) => id;

public int[] InterceptedIds([InterceptedID("Query")] [ID] int[] ids) => ids;

public string CustomId([ID] StronglyTypedId id) => id.ToString();

public string NullableCustomId([ID] StronglyTypedId? id) =>
id?.ToString() ?? "null";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,53 @@
{
{
"data": {
"intId": "1",
"nullableIntId": "1",
"nullableIntIdGivenNull": "null",
"optionalIntId": "1",
"optionalIntIdGivenNothing": "NO VALUE",
"intIdList": "1",
"nullableIntIdList": "1, 0",
"optionalIntIdList": "1",
"intId": 1,
"nullableIntId": 1,
"nullableIntIdGivenNull": null,
"optionalIntId": 1,
"optionalIntIdGivenNothing": null,
"intIdList": [
1
],
"nullableIntIdList": [
1,
null
],
"optionalIntIdList": [
1
],
"stringId": "abc",
"nullableStringId": "abc",
"nullableStringIdGivenNull": "null",
"nullableStringIdGivenNull": null,
"optionalStringId": "abc",
"optionalStringIdGivenNothing": "NO VALUE",
"stringIdList": "abc",
"nullableStringIdList": "abc, null",
"optionalStringIdList": "abc",
"optionalStringIdGivenNothing": null,
"stringIdList": [
"abc"
],
"nullableStringIdList": [
"abc",
null
],
"optionalStringIdList": [
"abc"
],
"guidId": "26a2dc8f-4dab-408c-88c6-523a0a89a2b5",
"nullableGuidId": "26a2dc8f-4dab-408c-88c6-523a0a89a2b5",
"nullableGuidIdGivenNull": "null",
"nullableGuidIdGivenNull": null,
"optionalGuidId": "26a2dc8f-4dab-408c-88c6-523a0a89a2b5",
"optionalGuidIdGivenNothing": "NO VALUE",
"guidIdList": "26a2dc8f-4dab-408c-88c6-523a0a89a2b5, 26a2dc8f-4dab-408c-88c6-523a0a89a2b5",
"nullableGuidIdList": "26a2dc8f-4dab-408c-88c6-523a0a89a2b5, 00000000-0000-0000-0000-000000000000, 26a2dc8f-4dab-408c-88c6-523a0a89a2b5",
"optionalGuidIdList": "26a2dc8f-4dab-408c-88c6-523a0a89a2b5, 26a2dc8f-4dab-408c-88c6-523a0a89a2b5",
"optionalGuidIdGivenNothing": null,
"guidIdList": [
"26a2dc8f-4dab-408c-88c6-523a0a89a2b5",
"26a2dc8f-4dab-408c-88c6-523a0a89a2b5"
],
"nullableGuidIdList": [
"26a2dc8f-4dab-408c-88c6-523a0a89a2b5",
null,
"26a2dc8f-4dab-408c-88c6-523a0a89a2b5"
],
"optionalGuidIdList": [
"26a2dc8f-4dab-408c-88c6-523a0a89a2b5",
"26a2dc8f-4dab-408c-88c6-523a0a89a2b5"
],
"customId": "1-2",
"nullableCustomId": "1-2",
"nullableCustomIdGivenNull": "null",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"result": "{\n \"data\": {\n \"foo\": {\n \"someId\": \"QmFyOjE=\",\n \"someNullableId\": null,\n \"someIds\": [\n \"QmF6OjE=\"\n ],\n \"someNullableIds\": [\n \"QmF6OjA=\",\n \"QmF6OjE=\"\n ]\n }\n }\n}",
{
"result": "{\n \"data\": {\n \"foo\": {\n \"someId\": \"QmFyOjE=\",\n \"someNullableId\": null,\n \"someIds\": [\n \"QmF6OjE=\"\n ],\n \"someNullableIds\": [\n null,\n \"QmF6OjE=\"\n ]\n }\n }\n}",
"someId": "U29tZTox",
"someIntId": "U29tZTox"
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
schema {
schema {
query: Query
}

Expand All @@ -23,26 +23,26 @@ type FooPayload implements IFooPayload {
}

type Query {
intId(id: ID!): String!
intIdList(id: [ID!]!): String!
nullableIntId(id: ID): String!
nullableIntIdList(id: [ID]!): String!
optionalIntId(id: ID = "UXVlcnk6MA=="): String!
optionalIntIdList(id: [ID!] = [ ]): String!
intId(id: ID!): Int!
intIdList(ids: [ID!]!): [Int!]!
nullableIntId(id: ID): Int
nullableIntIdList(ids: [ID]!): [Int]!
optionalIntId(id: ID = "UXVlcnk6MA=="): Int
optionalIntIdList(ids: [ID!] = [ ]): [Int!]
stringId(id: ID!): String!
stringIdList(id: [ID!]!): String!
nullableStringId(id: ID): String!
nullableStringIdList(id: [ID]!): String!
optionalStringId(id: ID = "UXVlcnk6"): String!
optionalStringIdList(id: [ID] = [ ]): String!
guidId(id: ID!): String!
guidIdList(id: [ID!]!): String!
nullableGuidId(id: ID): String!
nullableGuidIdList(id: [ID]!): String!
optionalGuidId(id: ID = "UXVlcnk6AAAAAAAAAAAAAAAAAAAAAA=="): String!
optionalGuidIdList(id: [ID] = [ ]): String!
interceptedId(id: ID!): String!
interceptedIds(id: [ID!]!): String!
stringIdList(ids: [ID!]!): [String!]!
nullableStringId(id: ID): String
nullableStringIdList(ids: [ID]!): [String]!
optionalStringId(id: ID = "UXVlcnk6"): String
optionalStringIdList(ids: [ID] = [ ]): [String!]
guidId(id: ID!): UUID!
guidIdList(ids: [ID!]!): [UUID!]!
nullableGuidId(id: ID): UUID
nullableGuidIdList(ids: [ID]!): [UUID]!
optionalGuidId(id: ID = "UXVlcnk6AAAAAAAAAAAAAAAAAAAAAA=="): UUID
optionalGuidIdList(ids: [ID] = [ ]): [UUID!]
interceptedId(id: ID!): Int!
interceptedIds(ids: [ID!]!): [Int!]!
customId(id: ID!): String!
nullableCustomId(id: ID): String!
customIds(ids: [ID!]!): String!
Expand All @@ -60,3 +60,8 @@ input FooInput {
interceptedId: ID
interceptedIds: [ID!]
}

"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions."
directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR

scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122")
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
{
"data": {
"interceptedId": "1",
"interceptedIds": "1, 2"
"interceptedId": 1,
"interceptedIds": [
1,
2
]
}
}

0 comments on commit d3da9b0

Please sign in to comment.