Skip to content

Commit

Permalink
Merge pull request #2 from graphql-dotnet/master
Browse files Browse the repository at this point in the history
Support for c# extensions (graphql-dotnet#136)
  • Loading branch information
rodolfotorres authored Mar 3, 2019
2 parents 4a47da2 + 48b5a70 commit 99541ce
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 4 deletions.
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

0 comments on commit 99541ce

Please sign in to comment.