diff --git a/deps/graphql-dotnet b/deps/graphql-dotnet index 6b806c2a..dc95b6f7 160000 --- a/deps/graphql-dotnet +++ b/deps/graphql-dotnet @@ -1 +1 @@ -Subproject commit 6b806c2aa3b6adfa974621a0ae3666430c603971 +Subproject commit dc95b6f788a0751d588e9f006a924e4650d690d0 diff --git a/src/GraphQL.Conventions/Adapters/Engine/GraphQLEngine.cs b/src/GraphQL.Conventions/Adapters/Engine/GraphQLEngine.cs index e7701249..867f3c96 100644 --- a/src/GraphQL.Conventions/Adapters/Engine/GraphQLEngine.cs +++ b/src/GraphQL.Conventions/Adapters/Engine/GraphQLEngine.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using GraphQL.Conventions.Adapters.Engine.Listeners.DataLoader; @@ -45,10 +46,34 @@ public INodeVisitor Validate(ValidationContext context) } } + private class WrappedDependencyInjector : IDependencyInjector + { + private readonly Func _typeResolutionDelegate; + + public WrappedDependencyInjector(Func typeResolutionDelegate) + { + _typeResolutionDelegate = typeResolutionDelegate; + } + + public object Resolve(System.Reflection.TypeInfo typeInfo) + { + return _typeResolutionDelegate(typeInfo.AsType()); + } + } + public GraphQLEngine(Func typeResolutionDelegate = null) { _constructor = new SchemaConstructor(_graphTypeAdapter, _typeResolver); - _constructor.TypeResolutionDelegate = typeResolutionDelegate; + if (typeResolutionDelegate != null) + { + _typeResolver.DependencyInjector = new WrappedDependencyInjector(typeResolutionDelegate); + _constructor.TypeResolutionDelegate = typeResolutionDelegate; + } + else + { + _constructor.TypeResolutionDelegate = type => + _typeResolver?.DependencyInjector?.Resolve(type.GetTypeInfo()) ?? Activator.CreateInstance(type); + } } public void BuildSchema(params System.Type[] schemaTypes) diff --git a/src/GraphQL.Conventions/Adapters/GraphTypeAdapter.cs b/src/GraphQL.Conventions/Adapters/GraphTypeAdapter.cs index 64744e25..cac2ff20 100644 --- a/src/GraphQL.Conventions/Adapters/GraphTypeAdapter.cs +++ b/src/GraphQL.Conventions/Adapters/GraphTypeAdapter.cs @@ -17,12 +17,20 @@ public class GraphTypeAdapter : IGraphTypeAdapter public ISchema DeriveSchema(GraphSchemaInfo schemaInfo) { - foreach (var type in schemaInfo.Types) + var types = schemaInfo + .Types + .Where(t => !t.IsIgnored && !t.Interfaces.Any(iface => iface.IsIgnored)) + .ToList(); + foreach (var type in types) { DeriveType(type); } - var interfaces = schemaInfo.Types.Where(t => !t.IsIgnored && t.IsInterfaceType && t.PossibleTypes.Any()); - var possibleTypes = interfaces.SelectMany(t => t.PossibleTypes).Distinct(); + var interfaces = types + .Where(t => t.IsInterfaceType && t.PossibleTypes.Any()); + var possibleTypes = interfaces + .Where(t => !t.IsIgnored) + .SelectMany(t => t.PossibleTypes) + .Distinct(); var schema = new Schema(DeriveTypeFromTypeInfo) { Query = DeriveOperationType(schemaInfo.Query), @@ -178,6 +186,10 @@ private TReturnType ConstructType(Type template, GraphTypeInfo type { var typeParameter = typeInfo.GetTypeRepresentation(); var type = CreateTypeInstance(template.MakeGenericType(typeParameter.AsType())); + if (type == null) + { + return default(TReturnType); + } type.Name = typeInfo.Name; type.Description = typeInfo.Description; type.DeprecationReason = typeInfo.DeprecationReason; @@ -197,6 +209,10 @@ private IGraphType ConstructEnumerationType(GraphTypeInfo typeInfo) private IGraphType ConstructInterfaceType(GraphTypeInfo typeInfo) { var graphType = ConstructType(typeof(Types.InterfaceGraphType<>), typeInfo); + if (typeInfo.IsIgnored) + { + return WrapNonNullableType(typeInfo, graphType); + } foreach (var possibleType in typeInfo.PossibleTypes.Select(t => DeriveType(t) as IObjectGraphType)) { graphType.AddPossibleType(possibleType); diff --git a/src/GraphQL.Conventions/CommonAssemblyInfo.cs b/src/GraphQL.Conventions/CommonAssemblyInfo.cs index f28fc0ad..076e7426 100644 --- a/src/GraphQL.Conventions/CommonAssemblyInfo.cs +++ b/src/GraphQL.Conventions/CommonAssemblyInfo.cs @@ -7,9 +7,9 @@ [assembly: AssemblyProduct("GraphQL.Conventions")] [assembly: AssemblyCopyright("Copyright 2016-2017 Tommy Lillehagen. All rights reserved.")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("1.0.7.0")] -[assembly: AssemblyFileVersion("1.0.7.0")] -[assembly: AssemblyInformationalVersion("1.0.7.0")] +[assembly: AssemblyVersion("1.0.8.0")] +[assembly: AssemblyFileVersion("1.0.8.0")] +[assembly: AssemblyInformationalVersion("1.0.8.0")] [assembly: CLSCompliant(false)] [assembly: InternalsVisibleTo("GraphQL.Conventions.Tests")] diff --git a/src/GraphQL.Conventions/Types/Descriptors/GraphArgumentInfo.cs b/src/GraphQL.Conventions/Types/Descriptors/GraphArgumentInfo.cs index fd26db87..b7c65eae 100644 --- a/src/GraphQL.Conventions/Types/Descriptors/GraphArgumentInfo.cs +++ b/src/GraphQL.Conventions/Types/Descriptors/GraphArgumentInfo.cs @@ -18,5 +18,7 @@ public GraphArgumentInfo(ITypeResolver typeResolver, ParameterInfo parameter = n new List(); public bool IsInjected { get; set; } + + public override string ToString() => $"{nameof(GraphArgumentInfo)}:{Name}"; } } diff --git a/src/GraphQL.Conventions/Types/Descriptors/GraphFieldInfo.cs b/src/GraphQL.Conventions/Types/Descriptors/GraphFieldInfo.cs index 7e48a428..4da2b577 100644 --- a/src/GraphQL.Conventions/Types/Descriptors/GraphFieldInfo.cs +++ b/src/GraphQL.Conventions/Types/Descriptors/GraphFieldInfo.cs @@ -23,5 +23,7 @@ public GraphFieldInfo(ITypeResolver typeResolver, MemberInfo field = null) new List(); public bool IsMethod => AttributeProvider is MethodInfo; + + public override string ToString() => $"{nameof(GraphFieldInfo)}:{Name}"; } } diff --git a/src/GraphQL.Conventions/Types/Descriptors/GraphSchemaInfo.cs b/src/GraphQL.Conventions/Types/Descriptors/GraphSchemaInfo.cs index 855dbe93..06cb7139 100644 --- a/src/GraphQL.Conventions/Types/Descriptors/GraphSchemaInfo.cs +++ b/src/GraphQL.Conventions/Types/Descriptors/GraphSchemaInfo.cs @@ -38,5 +38,7 @@ public object this[string key] } return defaultValue; } + + public override string ToString() => nameof(GraphSchemaInfo); } } diff --git a/src/GraphQL.Conventions/Types/Descriptors/GraphTypeInfo.cs b/src/GraphQL.Conventions/Types/Descriptors/GraphTypeInfo.cs index 1ab315fe..1a103018 100644 --- a/src/GraphQL.Conventions/Types/Descriptors/GraphTypeInfo.cs +++ b/src/GraphQL.Conventions/Types/Descriptors/GraphTypeInfo.cs @@ -86,6 +86,8 @@ public void AddUnionType(GraphTypeInfo typeInfo) AddPossibleType(typeInfo); } + public override string ToString() => $"{nameof(GraphTypeInfo)}:{Name}"; + private void DeriveMetaData() { var type = TypeRepresentation; diff --git a/src/GraphQL.Conventions/Types/Resolution/ObjectReflector.cs b/src/GraphQL.Conventions/Types/Resolution/ObjectReflector.cs index cb8ef4ab..73dc86b6 100644 --- a/src/GraphQL.Conventions/Types/Resolution/ObjectReflector.cs +++ b/src/GraphQL.Conventions/Types/Resolution/ObjectReflector.cs @@ -54,7 +54,7 @@ public GraphSchemaInfo GetSchema(TypeInfo typeInfo) schemaInfo.Subscription = subscriptionField != null ? GetType(subscriptionField.PropertyType.GetTypeInfo()) : null; - schemaInfo.Types.AddRange(_typeCache.Entities); + schemaInfo.Types.AddRange(_typeCache.Entities.Where(t => IsValidType(t.GetTypeRepresentation()))); return schemaInfo; } @@ -73,12 +73,21 @@ public GraphTypeInfo GetType(TypeInfo typeInfo) return type; } - var isInjectedType = type.TypeRepresentation.AsType() == typeof(IResolutionContext); + if (typeInfo.IsGenericParameter || typeInfo.ContainsGenericParameters) + { + type.IsIgnored = true; + return type; + } + + var isInjectedType = + type.TypeRepresentation.AsType() == typeof(IResolutionContext) || + type.TypeRepresentation.AsType() == typeof(IUserContext); if (!type.IsEnumerationType && !type.IsScalarType && !type.IsInterfaceType && !type.IsUnionType && + !type.IsIgnored && !isInjectedType) { DeriveInterfaces(type); @@ -93,14 +102,14 @@ public GraphTypeInfo GetType(TypeInfo typeInfo) _metaDataHandler.DeriveMetaData(type, GetTypeInfo(typeInfo)); - if (type.IsInterfaceType && !isInjectedType) + if (type.IsInterfaceType && !type.IsIgnored && !isInjectedType) { var iface = type.GetTypeRepresentation(); var types = iface.Assembly.GetTypes().Where(t => iface.IsAssignableFrom(t)); foreach (var t in types) { var ti = t.GetTypeInfo(); - if (!ti.IsInterface) + if (!ti.IsInterface && IsValidType(ti)) { GetType(ti); } @@ -190,6 +199,10 @@ private IEnumerable GetArguments(MethodInfo methodInfo) .Select(DeriveArgument) ?? new GraphArgumentInfo[0]) { + if (argument.IsInjected) + { + argument.Type.IsIgnored = true; + } yield return argument; } } @@ -272,7 +285,9 @@ private GraphEnumMemberInfo DeriveEnumValue(string name, TypeInfo type) private static bool IsValidType(TypeInfo typeInfo) { return typeInfo.Namespace != nameof(System) && - !typeInfo.Namespace.StartsWith($"{nameof(System)}."); + !typeInfo.Namespace.StartsWith($"{nameof(System)}.") && + !typeInfo.ContainsGenericParameters && + !typeInfo.IsGenericType; } private static bool IsValidMember(MemberInfo memberInfo) diff --git a/src/GraphQL.Conventions/project.json b/src/GraphQL.Conventions/project.json index 0babd94f..0dea3353 100755 --- a/src/GraphQL.Conventions/project.json +++ b/src/GraphQL.Conventions/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.7-*", + "version": "1.0.8-*", "description": "GraphQL Conventions for .NET", "authors": [ "Tommy Lillehagen" diff --git a/test/GraphQL.Conventions.Tests/Adapters/Engine/AbstractTypeConstructionTests.cs b/test/GraphQL.Conventions.Tests/Adapters/Engine/AbstractTypeConstructionTests.cs new file mode 100644 index 00000000..a4b6af3a --- /dev/null +++ b/test/GraphQL.Conventions.Tests/Adapters/Engine/AbstractTypeConstructionTests.cs @@ -0,0 +1,52 @@ +using System; +using GraphQL.Conventions.Adapters.Engine; +using GraphQL.Conventions.Tests.Templates; +using GraphQL.Conventions.Tests.Templates.Extensions; +using GraphQL.Conventions.Types; +using Xunit; + +namespace GraphQL.Conventions.Tests.Adapters.Engine +{ + public class AbstractTypeConstructionTests : TestBase + { + [Fact] + public void Can_Construct_And_Describe_Schema_From_Abstract_Types() + { + var engine = new GraphQLEngine(); + engine.BuildSchema(typeof(SchemaDefinition)); + var schema = engine.Describe(); + schema.ShouldEqualWhenReformatted(@" + type Query { + commonField: Date! + someOtherField: String + } + "); + } + + [Fact] + public async void Can_Execute_Query_On_Schema_From_Abstract_Types() + { + var engine = new GraphQLEngine(); + engine.BuildSchema(typeof(SchemaDefinition)); + var result = await engine + .NewExecutor() + .WithQueryString("{ commonField someOtherField }") + .EnableValidation() + .Execute(); + + result.ShouldHaveNoErrors(); + result.Data.ShouldHaveFieldWithValue("commonField", default(DateTime)); + result.Data.ShouldHaveFieldWithValue("someOtherField", string.Empty); + } + + abstract class EntityQuery + { + public T CommonField => default(T); + } + + class Query : EntityQuery + { + public string SomeOtherField => string.Empty; + } + } +} \ No newline at end of file diff --git a/test/GraphQL.Conventions.Tests/Adapters/Engine/DependencyInjectionTests.cs b/test/GraphQL.Conventions.Tests/Adapters/Engine/DependencyInjectionTests.cs new file mode 100644 index 00000000..b29b4825 --- /dev/null +++ b/test/GraphQL.Conventions.Tests/Adapters/Engine/DependencyInjectionTests.cs @@ -0,0 +1,130 @@ +using System.Reflection; +using GraphQL.Conventions.Adapters.Engine; +using GraphQL.Conventions.Attributes.MetaData; +using GraphQL.Conventions.Tests.Templates; +using GraphQL.Conventions.Tests.Templates.Extensions; +using GraphQL.Conventions.Types; +using GraphQL.Conventions.Types.Resolution; +using Xunit; + +namespace GraphQL.Conventions.Tests.Adapters.Engine +{ + public class DependencyInjectionTests : TestBase + { + [Fact] + public void Can_Construct_And_Describe_Schema_With_Injections() + { + var engine = new GraphQLEngine(); + engine.BuildSchema(typeof(SchemaDefinition)); + var schema = engine.Describe(); + schema.ShouldEqualWhenReformatted(@" + type Query { + field: String + } + "); + } + + [Fact] + public async void Can_Execute_Query_On_Schema_With_Injections() + { + var engine = new GraphQLEngine(); + engine.BuildSchema(typeof(SchemaDefinition)); + var result = await engine + .NewExecutor() + .WithQueryString("{ field }") + .WithDependencyInjector(new DependencyInjector()) + .Execute(); + + result.ShouldHaveNoErrors(); + result.Data.ShouldHaveFieldWithValue("field", "Some Value"); + } + + [Fact] + public void Can_Construct_And_Describe_Schema_With_Injections_In_Generic_Methods() + { + var engine = new GraphQLEngine(); + engine.BuildSchema(typeof(SchemaDefinition)); + var schema = engine.Describe(); + schema.ShouldEqualWhenReformatted(@" + schema { + query: QueryWithDIFields + + } + type QueryWithDIFields { + withDependency: Int! + } + "); + } + + [Fact] + public async void Can_Execute_Query_On_Schema_With_Injections_In_Generic_Methods() + { + var engine = new GraphQLEngine(); + engine.BuildSchema(typeof(SchemaDefinition)); + var result = await engine + .NewExecutor() + .WithQueryString("{ withDependency }") + .WithDependencyInjector(new DependencyInjector()) + .Execute(); + + result.ShouldHaveNoErrors(); + result.Data.ShouldHaveFieldWithValue("withDependency", 3); + } + + class Query + { + private readonly IRepository _repository; + + public Query(IRepository repository) + { + _repository = repository; + } + + public string Field => _repository.GetValue(); + } + + interface IRepository + { + string GetValue(); + } + + class Repository : IRepository + { + public string GetValue() + { + return "Some Value"; + } + } + + class QueryWithDIFields + { + public int WithDependency([Inject] IDependency d) => d.Get(3); + } + + interface IDependency + { + T Get(T value); + } + + class Dependency : IDependency + { + public T Get(T value) => value; + } + + class DependencyInjector : IDependencyInjector + { + public object Resolve(TypeInfo typeInfo) + { + if (typeInfo.AsType() == typeof(Query)) + { + return new Query(new Repository()); + } + if (typeInfo.AsType() == typeof(IDependency)) + { + return new Dependency(); + } + return null; + } + } + } +} \ No newline at end of file diff --git a/test/GraphQL.Conventions.Tests/Adapters/Engine/DynamicConstructionTests.cs b/test/GraphQL.Conventions.Tests/Adapters/Engine/DynamicConstructionTests.cs index 5a8e94bf..86534004 100644 --- a/test/GraphQL.Conventions.Tests/Adapters/Engine/DynamicConstructionTests.cs +++ b/test/GraphQL.Conventions.Tests/Adapters/Engine/DynamicConstructionTests.cs @@ -16,7 +16,7 @@ namespace GraphQL.Conventions.Tests.Adapters.Engine public class DynamicConstructionTests : TestBase { [Fact] - public async Task Can_Construct_And_Describe_Schema_With_Dynamic_Queries() + public async void Can_Construct_And_Describe_Schema_With_Dynamic_Queries() { var typeAdapter = new GraphTypeAdapter(); var typeResolver = new TypeResolver(); @@ -75,8 +75,9 @@ type UserRepository implements IUserRepository { var executer = new DocumentExecuter(); var result = await executer.ExecuteAsync(schema, null, "{ users { getUserById(id: \"1\") { id name } } }", null); - result.Errors.ShouldEqual(null); - JsonConvert.SerializeObject(result.Data).ShouldEqual("{\"users\":{\"getUserById\":{\"id\":\"1\",\"name\":\"User #1\"}}}"); + result.ShouldHaveNoErrors(); + result.Data.ShouldHaveFieldWithValue("users", "getUserById", "id", "1"); + result.Data.ShouldHaveFieldWithValue("users", "getUserById", "name", "User #1"); } private IGraphType GetGraphType(GraphTypeAdapter typeAdapter, TypeResolver typeResolver, TypeInfo typeInfo)