Skip to content

Commit ab81a5d

Browse files
authored
Add the ability to return a GraphQLTypeReference from ResolveType (graphql-dotnet#775)
1 parent 954698f commit ab81a5d

File tree

11 files changed

+127
-41
lines changed

11 files changed

+127
-41
lines changed

src/GraphQL.Tests/Execution/AbstractTypeTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public void throws_when_unable_to_determine_object_type()
1212
{
1313
var result = AssertQueryWithErrors("{ pets { name } }", "{ 'pets': null }", expectedErrorCount: 1);
1414
var error = result.Errors.First();
15-
error.Message.ShouldBe("Abstract type Pet must resolve to an Object type at runtime for field Query.pets with value { name = Eli }, received 'null'.");
15+
error.Message.ShouldBe("Abstract type Pet must resolve to an Object type at runtime for field Query.pets with value '{ name = Eli }', received 'null'.");
1616
}
1717
}
1818

src/GraphQL.Tests/Utilities/CustomGraphQLAttributeTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ type Query
8585
}
8686
";
8787

88-
Builder.Types.Include<ABlog>();
88+
Builder.Types.Include<ABlog, ABlog>();
8989

9090
var schema = Builder.Build(defs);
9191
schema.Initialize();

src/GraphQL.Tests/Utilities/SchemaBuilderNestedTypesTests.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using GraphQL;
3+
using GraphQL.Types;
34
using Xunit;
45

56
namespace GraphQL.Tests.Utilities
@@ -26,7 +27,40 @@ type Query {
2627
";
2728

2829
Builder.Types.Include<DroidType>();
29-
Builder.Types.For("Droid").IsTypeOf<Droid>();
30+
Builder.Types.Include<Query>();
31+
32+
var query = @"{ hero { id name friend { name } } }";
33+
var expected = @"{ 'hero': { 'id' : '1', 'name': 'R2-D2', 'friend': { 'name': 'C3-PO' } } }";
34+
35+
AssertQuery(_ =>
36+
{
37+
_.Query = query;
38+
_.Definitions = defs;
39+
_.ExpectedResult = expected;
40+
});
41+
}
42+
43+
[Fact]
44+
public void supports_type_references_in_resolve_type()
45+
{
46+
var defs = @"
47+
type Droid {
48+
id: String
49+
name: String
50+
friend: Character
51+
}
52+
53+
type Character {
54+
name: String
55+
}
56+
57+
type Query {
58+
hero: Droid
59+
}
60+
";
61+
62+
Builder.Types.Include<DroidType>();
63+
Builder.Types.For("Droid").ResolveType = obj => new GraphQLTypeReference("Droid");
3064
Builder.Types.Include<Query>();
3165

3266
var query = @"{ hero { id name friend { name } } }";
@@ -55,7 +89,7 @@ public class MyUserContext
5589
{
5690
}
5791

58-
[GraphQLMetadata("Droid")]
92+
[GraphQLMetadata("Droid", IsTypeOf = typeof(Droid))]
5993
public class DroidType
6094
{
6195
public string Id(Droid droid) => droid.Id;

src/GraphQL.Tests/Utilities/SchemaBuilderTestBase.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,7 @@ public ExecutionResult CreateQueryResult(string result)
6363
object expected = null;
6464
if (!string.IsNullOrWhiteSpace(result))
6565
{
66-
expected = JObject.Parse(result); /*/JsonConvert.SerializeObject(
67-
result,
68-
new JsonSerializerSettings
69-
{
70-
// ContractResolver = new CamelCasePropertyNamesContractResolver(),
71-
Formatting = Formatting.Indented,
72-
DateFormatHandling = DateFormatHandling.IsoDateFormat,
73-
DateFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFF'Z'",
74-
});*/
66+
expected = JObject.Parse(result);
7567
}
7668
return new ExecutionResult { Data = expected };
7769
}

src/GraphQL/Execution/ExecutionNode.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,15 @@ protected ExecutionNode(ExecutionNode parent, IGraphType graphType, Field field,
4646

4747
public abstract object ToValue();
4848

49-
public IObjectGraphType GetParentType()
49+
public IObjectGraphType GetParentType(ISchema schema)
5050
{
5151
IGraphType parentType = Parent?.GraphType;
5252

5353
if (parentType is IObjectGraphType objectType)
5454
return objectType;
5555

5656
if (parentType is IAbstractGraphType abstractType && Parent.IsResultSet)
57-
return abstractType.GetObjectType(Parent.Result);
57+
return abstractType.GetObjectType(Parent.Result, schema);
5858

5959
return null;
6060
}
@@ -72,15 +72,14 @@ public class ObjectExecutionNode : ExecutionNode, IParentExecutionNode
7272
public ObjectExecutionNode(ExecutionNode parent, IGraphType graphType, Field field, FieldType fieldDefinition, string[] path)
7373
: base(parent, graphType, field, fieldDefinition, path)
7474
{
75-
7675
}
7776

78-
public IObjectGraphType GetObjectGraphType()
77+
public IObjectGraphType GetObjectGraphType(ISchema schema)
7978
{
8079
var objectGraphType = GraphType as IObjectGraphType;
8180

8281
if (GraphType is IAbstractGraphType abstractGraphType && IsResultSet)
83-
objectGraphType = abstractGraphType.GetObjectType(Result);
82+
objectGraphType = abstractGraphType.GetObjectType(Result, schema);
8483

8584
return objectGraphType;
8685
}

src/GraphQL/Execution/ExecutionStrategy.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,13 @@ public static RootExecutionNode BuildExecutionRootNode(ExecutionContext context,
5050

5151
public static void SetSubFieldNodes(ExecutionContext context, ObjectExecutionNode parent)
5252
{
53-
54-
var fields = CollectFields(context, parent.GetObjectGraphType(), parent.Field?.SelectionSet);
55-
53+
var fields = CollectFields(context, parent.GetObjectGraphType(context.Schema), parent.Field?.SelectionSet);
5654
SetSubFieldNodes(context, parent, fields);
5755
}
5856

5957
public static void SetSubFieldNodes(ExecutionContext context, ObjectExecutionNode parent, Dictionary<string, Field> fields)
6058
{
61-
var parentType = parent.GetObjectGraphType();
59+
var parentType = parent.GetObjectGraphType(context.Schema);
6260

6361
var subFields = new Dictionary<string, ExecutionNode>();
6462

@@ -172,7 +170,7 @@ protected virtual async Task<ExecutionNode> ExecuteNodeAsync(ExecutionContext co
172170
FieldAst = node.Field,
173171
FieldDefinition = node.FieldDefinition,
174172
ReturnType = node.FieldDefinition.ResolvedType,
175-
ParentType = node.GetParentType(),
173+
ParentType = node.GetParentType(context.Schema),
176174
Arguments = arguments,
177175
Source = node.Source,
178176
Schema = context.Schema,
@@ -260,14 +258,14 @@ protected virtual void ValidateNodeResult(ExecutionContext context, ExecutionNod
260258

261259
if (fieldType is IAbstractGraphType abstractType)
262260
{
263-
objectType = abstractType.GetObjectType(result);
261+
objectType = abstractType.GetObjectType(result, context.Schema);
264262

265263
if (objectType == null)
266264
{
267265
var error = new ExecutionError(
268266
$"Abstract type {abstractType.Name} must resolve to an Object type at " +
269267
$"runtime for field {node.Parent.GraphType.Name}.{node.Name} " +
270-
$"with value {result}, received 'null'.");
268+
$"with value '{result}', received 'null'.");
271269
throw error;
272270
}
273271

@@ -280,7 +278,7 @@ protected virtual void ValidateNodeResult(ExecutionContext context, ExecutionNod
280278

281279
if (objectType?.IsTypeOf != null && !objectType.IsTypeOf(result))
282280
{
283-
var error = new ExecutionError($"Expected value of type \"{objectType}\" but got: {result}.");
281+
var error = new ExecutionError($"Expected value of type \"{objectType}\" for \"{objectType.Name}\" but got: {result}.");
284282
throw error;
285283
}
286284
}

src/GraphQL/Types/GraphQLTypeReference.cs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
namespace GraphQL.Types
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace GraphQL.Types
25
{
3-
public class GraphQLTypeReference : InterfaceGraphType
6+
public class GraphQLTypeReference : InterfaceGraphType, IObjectGraphType
47
{
58
public GraphQLTypeReference(string typeName)
69
{
@@ -10,6 +13,41 @@ public GraphQLTypeReference(string typeName)
1013

1114
public string TypeName { get; private set; }
1215

16+
public Func<object, bool> IsTypeOf
17+
{
18+
get {
19+
throw new InvalidOperationException("This is just a reference. Resolve the real type first.");
20+
}
21+
set {
22+
throw new InvalidOperationException("This is just a reference. Resolve the real type first.");
23+
}
24+
}
25+
26+
public void AddResolvedInterface(IInterfaceGraphType graphType)
27+
{
28+
throw new InvalidOperationException("This is just a reference. Resolve the real type first.");
29+
}
30+
31+
public IEnumerable<Type> Interfaces
32+
{
33+
get {
34+
throw new InvalidOperationException("This is just a reference. Resolve the real type first.");
35+
}
36+
set {
37+
throw new InvalidOperationException("This is just a reference. Resolve the real type first.");
38+
}
39+
}
40+
41+
public IEnumerable<IInterfaceGraphType> ResolvedInterfaces
42+
{
43+
get {
44+
throw new InvalidOperationException("This is just a reference. Resolve the real type first.");
45+
}
46+
set {
47+
throw new InvalidOperationException("This is just a reference. Resolve the real type first.");
48+
}
49+
}
50+
1351
public override bool Equals(object obj)
1452
{
1553
if (obj is GraphQLTypeReference other)
@@ -18,5 +56,10 @@ public override bool Equals(object obj)
1856
}
1957
return base.Equals(obj);
2058
}
59+
60+
public override int GetHashCode()
61+
{
62+
return TypeName?.GetHashCode() ?? 0;
63+
}
2164
}
2265
}

src/GraphQL/Types/IAbstractGraphType.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,23 @@ public interface IAbstractGraphType : IGraphType
1313
void AddPossibleType(IObjectGraphType type);
1414
}
1515

16-
public static class AbstractGraphTypeExtensions {
17-
16+
public static class AbstractGraphTypeExtensions
17+
{
1818
public static bool IsPossibleType(this IAbstractGraphType abstractType, IGraphType type)
1919
{
2020
return abstractType.PossibleTypes.Any(x => x.Equals(type));
2121
}
2222

23-
public static IObjectGraphType GetObjectType(this IAbstractGraphType abstractType, object value)
23+
public static IObjectGraphType GetObjectType(this IAbstractGraphType abstractType, object value, ISchema schema)
2424
{
25-
return abstractType.ResolveType != null
25+
var result = abstractType.ResolveType != null
2626
? abstractType.ResolveType(value)
2727
: GetTypeOf(abstractType, value);
28+
29+
if (result is GraphQLTypeReference reference)
30+
result = schema.FindType(reference.Name) as IObjectGraphType;
31+
32+
return result;
2833
}
2934

3035
public static IObjectGraphType GetTypeOf(this IAbstractGraphType abstractType, object value)

src/GraphQL/Utilities/TypeConfig.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ public Type Type
2626
{
2727
_type = value;
2828
ApplyMetadata(value);
29-
30-
if (IsTypeOfFunc == null)
31-
IsTypeOfFunc = obj => obj?.GetType().IsAssignableFrom(_type) ?? false;
3229
}
3330
}
3431

@@ -40,7 +37,7 @@ public Type Type
4037

4138
public void IsTypeOf<T>()
4239
{
43-
IsTypeOfFunc = obj => obj?.GetType() == typeof(T);
40+
IsTypeOfFunc = obj => obj?.GetType().IsAssignableFrom(typeof(T)) ?? false;
4441
}
4542

4643
public FieldConfig FieldFor(string field, IDependencyResolver dependencyResolver)

src/GraphQL/Utilities/TypeSettings.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Reflection;
23

34
namespace GraphQL.Utilities
45
{
@@ -17,10 +18,9 @@ public TypeConfig For(string typeName)
1718
return _typeConfigurations[typeName];
1819
}
1920

20-
public void Include<T>()
21+
public void Include<TType>()
2122
{
22-
var type = typeof(T);
23-
Include(type);
23+
Include(typeof(TType));
2424
}
2525

2626
public void Include(Type type)
@@ -33,5 +33,23 @@ public void Include(string name, Type type)
3333
{
3434
_typeConfigurations[name].Type = type;
3535
}
36+
37+
public void Include<TType, TTypeOfType>()
38+
{
39+
Include(typeof(TType), typeof(TTypeOfType));
40+
}
41+
42+
public void Include(Type type, Type typeOfType)
43+
{
44+
var name = type.GraphQLName();
45+
Include(name, type, typeOfType);
46+
}
47+
48+
public void Include(string name, Type type, Type typeOfType)
49+
{
50+
var config = _typeConfigurations[name];
51+
config.Type = type;
52+
config.IsTypeOfFunc = obj => obj?.GetType().IsAssignableFrom(typeOfType) ?? false;
53+
}
3654
}
37-
}
55+
}

tools/tasks/test.dotnet.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import exec from './exec'
44
function test(settings, project) {
55
return () => {
66
const platform = process.platform === 'darwin'
7-
? '-f netcoreapp2.0'
7+
? '-f netcoreapp2.1'
88
: ''
99
const cmd = `dotnet test ${platform} "${project}" -c ${settings.target} --no-restore`
1010
return exec(cmd)

0 commit comments

Comments
 (0)