Skip to content
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

Support for c# extensions #136

Merged
merged 2 commits into from
Mar 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<ItemGroup>
<PackageReference Include="AutoMapper" Version="7.0.1" />
<PackageReference Include="GraphQL.Conventions" Version="2.0.1" />
<PackageReference Include="GraphQL.Conventions" Version="2.0.2" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.4" />
Expand Down
5 changes: 5 additions & 0 deletions src/GraphQL.Conventions/Adapters/Resolvers/FieldResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ private object CallMethod(GraphFieldInfo fieldInfo, IResolutionContext context)
.Arguments
.Select(arg => context.GetArgument(arg));

if (fieldInfo.IsExtensionMethod)
{
arguments = new[] { source }.Concat(arguments);
}

return methodInfo?.Invoke(source, arguments.ToArray());
}

Expand Down
4 changes: 2 additions & 2 deletions src/GraphQL.Conventions/CommonAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
[assembly: AssemblyCopyright("Copyright 2016-2017 Tommy Lillehagen. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("2.0.1")]
[assembly: AssemblyInformationalVersion("2.0.1")]
[assembly: AssemblyFileVersion("2.0.2")]
[assembly: AssemblyInformationalVersion("2.0.2")]
[assembly: CLSCompliant(false)]

[assembly: InternalsVisibleTo("Tests")]
2 changes: 1 addition & 1 deletion src/GraphQL.Conventions/GraphQL.Conventions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>GraphQL Conventions for .NET</Description>
<VersionPrefix>2.0.1</VersionPrefix>
<VersionPrefix>2.0.2</VersionPrefix>
<Authors>Tommy Lillehagen</Authors>
<TargetFrameworks>netstandard1.6.1;net45</TargetFrameworks>
<DebugType>portable</DebugType>
Expand Down
3 changes: 3 additions & 0 deletions src/GraphQL.Conventions/Types/Descriptors/GraphFieldInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Reflection;
using GraphQL.Conventions.Attributes;
using GraphQL.Conventions.Types.Resolution;
using GraphQL.Conventions.Types.Resolution.Extensions;

namespace GraphQL.Conventions.Types.Descriptors
{
Expand Down Expand Up @@ -30,6 +31,8 @@ public object DefaultValue

public bool IsMethod => AttributeProvider is MethodInfo;

public bool IsExtensionMethod => (AttributeProvider as MethodInfo).IsExtensionMethod();

public override string ToString() => $"{nameof(GraphFieldInfo)}:{Name}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using GraphQL.Conventions.Types.Descriptors;

Expand Down Expand Up @@ -110,5 +111,10 @@ public static object ConvertToArrayRuntime(this IList list, Type elementType)
var genericMethod = convertMethod.MakeGenericMethod(elementType);
return genericMethod.Invoke(null, new object[] {list});
}

public static bool IsExtensionMethod(this MethodInfo methodInfo)
{
return methodInfo?.IsDefined(typeof(ExtensionAttribute), false) ?? false;
}
}
}
3 changes: 3 additions & 0 deletions src/GraphQL.Conventions/Types/Resolution/ITypeResolver.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Reflection;
using GraphQL.Conventions.Types.Descriptors;

Expand All @@ -21,6 +22,8 @@ public interface ITypeResolver

void IgnoreTypesFromNamespacesStartingWith(params string[] namespacesToIgnore);

void AddExtensions(Type typeExtensions);

GraphSchemaInfo ActiveSchema { get; set; }
}
}
31 changes: 31 additions & 0 deletions src/GraphQL.Conventions/Types/Resolution/ObjectReflector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,28 @@ class ObjectReflector

private readonly MetaDataAttributeHandler _metaDataHandler = new MetaDataAttributeHandler();

private readonly Dictionary<Type, List<MethodInfo>> _typeExtensionMethods = new Dictionary<Type, List<MethodInfo>>();

public HashSet<string> IgnoredNamespaces { get; } = new HashSet<string>() { nameof(System) + "." };

public ObjectReflector(ITypeResolver typeResolver)
{
_typeResolver = typeResolver;
}

public void AddExtensions(TypeInfo typeExtensions)
{
var extensionMethods = typeExtensions
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.IsExtensionMethod())
.GroupBy(m => m.GetParameters()[0].ParameterType);

foreach (var methodGroup in extensionMethods)
{
GetExtensionMethods(methodGroup.Key).AddRange(methodGroup);
}
}

public GraphSchemaInfo GetSchema(TypeInfo typeInfo)
{
var queryField = typeInfo.GetProperty("Query");
Expand Down Expand Up @@ -185,6 +200,20 @@ private TypeInfo GetTypeInfo(GraphTypeInfo type) =>
private TypeInfo GetTypeInfo(ICustomAttributeProvider attributeProvider) =>
((TypeInfo)attributeProvider).GetTypeRepresentation();

private List<MethodInfo> GetExtensionMethods(TypeInfo typeInfo)
=> GetExtensionMethods(typeInfo.UnderlyingSystemType);

private List<MethodInfo> GetExtensionMethods(Type type)
{
List<MethodInfo> methods;
if (!_typeExtensionMethods.TryGetValue(type, out methods))
{
methods = new List<MethodInfo>();
_typeExtensionMethods.Add(type, methods);
}
return methods;
}

private IEnumerable<GraphFieldInfo> GetFields(TypeInfo typeInfo)
{
var implementedProperties = typeInfo
Expand All @@ -204,6 +233,7 @@ private IEnumerable<GraphFieldInfo> GetFields(TypeInfo typeInfo)
return typeInfo
.GetMethods(DefaultBindingFlags)
.Union(implementedMethods)
.Union(GetExtensionMethods(typeInfo))
.Where(IsValidMember)
.Where(methodInfo => !methodInfo.IsSpecialName)
.Cast<MemberInfo>()
Expand All @@ -217,6 +247,7 @@ private IEnumerable<GraphArgumentInfo> GetArguments(MethodInfo methodInfo)
{
foreach (var argument in methodInfo?
.GetParameters()
.Skip(methodInfo.IsExtensionMethod() ? 1 : 0)
.Select(DeriveArgument)
?? new GraphArgumentInfo[0])
{
Expand Down
3 changes: 3 additions & 0 deletions src/GraphQL.Conventions/Types/Resolution/TypeResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,8 @@ public void IgnoreTypesFromNamespacesStartingWith(params string[] namespacesToIg
foreach (var @namespace in namespacesToIgnore.Distinct())
_reflector.IgnoredNamespaces.Add(@namespace);
}

public void AddExtensions(Type typeExtensions) =>
_reflector.AddExtensions(typeExtensions.GetTypeInfo());
}
}
6 changes: 6 additions & 0 deletions src/GraphQL.Conventions/Web/RequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ public RequestHandlerBuilder WithQueryAndMutation<TQuery, TMutation>()
return this;
}

public RequestHandlerBuilder WithQueryExtensions(Type typeExtensions)
{
_typeResolver.AddExtensions(typeExtensions);
return this;
}

public RequestHandlerBuilder WithSubscription<TSubscription>()
{
_schemaTypes.Add(typeof(SchemaDefinitionWithSubscription<TSubscription>));
Expand Down
82 changes: 82 additions & 0 deletions test/Tests/Web/RequestHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,64 @@ public async Task Can_Ignore_Types_From_Unwanted_Namespaces()
response.Body.ShouldContain("VALIDATION_ERROR");
}

[Test]
public async Task Can_Run_Query_With_Type_Extensions()
{
var request = Request.New("{ \"query\": \"{ helloExtended(v: 10) }\" }");
var response = await RequestHandler
.New()
.WithQuery<SimpleQuery>()
.WithQueryExtensions(typeof(QueryExtensions))
.Generate()
.ProcessRequest(request, null, null);

response.ExecutionResult.Data.ShouldHaveFieldWithValue("helloExtended", "Extended-10");
response.Body.ShouldEqual("{\"data\":{\"helloExtended\":\"Extended-10\"}}");
response.Errors.Count.ShouldEqual(0);
response.Warnings.Count.ShouldEqual(0);
}

[Test]
public async Task Can_Run_Query_With_Nested_Type_Extensions()
{
var request = Request.New("{ \"query\": \"{ helloType(v: 1) { myName }}\" }");
var response = await RequestHandler
.New()
.WithQuery<SimpleQuery>()
.WithQueryExtensions(typeof(QueryExtensions))
.Generate()
.ProcessRequest(request, null, null);

response.Body.ShouldEqual("{\"data\":{\"helloType\":{\"myName\":\"Name-1\"}}}");
response.Errors.Count.ShouldEqual(0);
response.Warnings.Count.ShouldEqual(0);
}

[Test]
public void Can_Construct_And_Describe_Schema_With_Extensions()
{
var schema = RequestHandler
.New()
.WithQuery<SimpleQuery>()
.WithQueryExtensions(typeof(QueryExtensions))
.Generate()
.DescribeSchema(false, false, false);

schema.ShouldEqualWhenReformatted(@"
schema {
query: SimpleQuery
}
type HelloType {
myName: String
}
type SimpleQuery {
hello: String
helloExtended(v: Int!): String
helloType(v: Int!): HelloType
}
");
}

class TestQuery
{
public string Hello => "World";
Expand Down Expand Up @@ -126,6 +184,30 @@ class CompositeQuery
}
}

class SimpleQuery
{
public string Hello => "World";
public HelloType HelloType(int v) => new HelloType(v);
}

class HelloType
{
[Ignore]
public int HiddenVersion { get; set; }

public HelloType(int v)
{
HiddenVersion = v;
}
}

static class QueryExtensions
{
public static string HelloExtended(this SimpleQuery query, int v) => $"Extended-{v}";

public static string MyName(this HelloType helloType) => $"Name-{helloType.HiddenVersion}";
}

namespace Unwanted
{
class TestQuery2
Expand Down