-
-
Notifications
You must be signed in to change notification settings - Fork 781
Support lists in external reference resolvers #8001
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
using System.Collections; | ||
using System.Reflection; | ||
using System.Runtime.CompilerServices; | ||
using HotChocolate.Language; | ||
using HotChocolate.Utilities; | ||
|
@@ -62,6 +64,45 @@ private static bool TryGetValue<T>( | |
value = default; | ||
return true; | ||
} | ||
case SyntaxKind.ListValue: | ||
{ | ||
if (type is not ListType listType) | ||
{ | ||
break; | ||
} | ||
|
||
var list = CreateList(listType); | ||
var items = ((ListValueNode)valueNode).Items; | ||
var flatList = !listType.ElementType.IsListType(); | ||
var elementType = type.ElementType(); | ||
var elementClrType = typeof(T).GetGenericArguments().Single()!; | ||
|
||
var innerTryGetValueMethod = typeof(ArgumentParser) | ||
.GetMethod(nameof(TryGetValue), BindingFlags.NonPublic | BindingFlags.Static)! | ||
.MakeGenericMethod(elementClrType); | ||
|
||
if (flatList) | ||
{ | ||
for (var j = 0; j < items.Count; j++) | ||
{ | ||
var parameters = new object?[] { items[j], elementType, path, i + 1, null }; | ||
var succeeded = (bool) innerTryGetValueMethod.Invoke(null, parameters)!; | ||
if (!succeeded) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
var innerValue = parameters[4]!; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like this way of doing things, but I'm not familiar enough with dotnet reflection to know a better way of doing it. From what I can tell:
To me this feels quite fragile, as if the arguments for this method change, this will break. This should be caught by the test added in this PR, but I'm open to other ways of implementing this that'd be more elegant! |
||
list.Add(innerValue); | ||
} | ||
} | ||
else | ||
{ | ||
throw new NotImplementedException(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not really sure how to handle nested lists in this case. Does it actually matter? I think the logic just works the same way. I'll try adding another test case for this to confirm it works. |
||
} | ||
|
||
value = (T)list; | ||
return true; | ||
} | ||
case SyntaxKind.StringValue: | ||
case SyntaxKind.IntValue: | ||
case SyntaxKind.FloatValue: | ||
|
@@ -99,6 +140,9 @@ private static bool TryGetValue<T>( | |
return false; | ||
} | ||
|
||
private static IList CreateList(ListType type) | ||
=> (IList)Activator.CreateInstance(type.ToRuntimeType())!; | ||
|
||
public static bool Matches(IValueNode valueNode, IReadOnlyList<string[]> required) | ||
{ | ||
if (required.Count == 1) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
using HotChocolate.ApolloFederation.Types; | ||
using HotChocolate.Execution; | ||
using HotChocolate.Language; | ||
using HotChocolate.Resolvers; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Moq; | ||
using static HotChocolate.ApolloFederation.TestHelper; | ||
|
@@ -71,6 +72,36 @@ public async Task TestResolveViaForeignServiceType_MixedTypes() | |
Assert.Equal("InternalValue", obj.InternalField); | ||
} | ||
|
||
[Fact] | ||
public async Task TestResolveViaForeignServiceType_ListType() | ||
{ | ||
// arrange | ||
var schema = await new ServiceCollection() | ||
.AddGraphQL() | ||
.AddApolloFederation() | ||
.AddQueryType<Query>() | ||
.BuildSchemaAsync(); | ||
|
||
var context = CreateResolverContext(schema); | ||
|
||
// act | ||
var representations = new List<Representation> | ||
{ | ||
new("ListFieldType", | ||
new ObjectValueNode( | ||
new ObjectFieldNode("id", "1"), | ||
new ObjectFieldNode("listField", new ListValueNode(new List<IntValueNode> { new(1), new(2), new(3) })))), | ||
}; | ||
|
||
// assert | ||
var result = | ||
await EntitiesResolver.ResolveAsync(schema, representations, context); | ||
var obj = Assert.IsType<ListFieldType>(result[0]); | ||
Assert.Equal("1", obj.Id); | ||
Assert.Equal([1, 2, 3], obj.ListField); | ||
Assert.Equal("InternalValue", obj.InternalField); | ||
} | ||
|
||
[Fact] | ||
public async Task TestResolveViaEntityResolver() | ||
{ | ||
|
@@ -249,6 +280,7 @@ public class Query | |
public TypeWithoutRefResolver TypeWithoutRefResolver { get; set; } = default!; | ||
public MixedFieldTypes MixedFieldTypes { get; set; } = default!; | ||
public FederatedType TypeWithReferenceResolverMany { get; set; } = default!; | ||
public ListFieldType ListFieldType { get; set; } = default!; | ||
} | ||
|
||
public class TypeWithoutRefResolver | ||
|
@@ -335,6 +367,31 @@ public class FederatedType | |
} | ||
} | ||
|
||
[ExtendServiceType] | ||
public class ListFieldType | ||
{ | ||
public ListFieldType(string id, List<double> listField) | ||
{ | ||
Id = id; | ||
ListField = listField; | ||
} | ||
|
||
[Key] | ||
[External] | ||
public string Id { get; } | ||
|
||
[External] | ||
public List<double> ListField { get; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've tested this locally by swapping out various types of lists, but I'm happy to add in a more comprehensive set of tests depending on what you'd prefer eg:
|
||
|
||
public string InternalField { get; set; } = "InternalValue"; | ||
|
||
[ReferenceResolver] | ||
public static ListFieldType GetByExternal(string id, List<double> listField, IResolverContext context) | ||
{ | ||
return new(id, listField); | ||
} | ||
} | ||
|
||
public class FederatedTypeDataLoader : BatchDataLoader<string, FederatedType> | ||
{ | ||
public int TimesCalled { get; private set; } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% clear on the signature for the function - if we fail to parse any of the list elements, should we short-circuit and just return false? Do we need to make any changes to
value
?