Skip to content

Commit

Permalink
Patch enumerable support (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shane32 authored Apr 24, 2022
1 parent b38149b commit 480dda0
Showing 1 changed file with 183 additions and 0 deletions.
183 changes: 183 additions & 0 deletions src/GraphQL.DI/DIObjectGraphType.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using GraphQL.DataLoader;
using GraphQL.Types;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -93,5 +95,186 @@ protected override ArgumentInformation GetArgumentInformation<TParameterType>(Fi
argumentInfo.ApplyAttributes();
return argumentInfo;
}

#region Patch for IEnumerable
/// <inheritdoc/>
protected override TypeInformation GetTypeInformation(MemberInfo memberInfo)
{
var typeInformation = memberInfo switch
{
PropertyInfo propertyInfo => new MyTypeInformation(propertyInfo, false),
MethodInfo methodInfo => new MyTypeInformation(methodInfo),
FieldInfo fieldInfo => new MyTypeInformation(fieldInfo, false),
_ => throw new ArgumentOutOfRangeException(nameof(memberInfo), "Only properties, methods and fields are supported."),
};
typeInformation.ApplyAttributes();
return typeInformation;
}

private class MyTypeInformation : TypeInformation
{
public MyTypeInformation(PropertyInfo propertyInfo, bool isInput) : base(propertyInfo, isInput) { }
public MyTypeInformation(MethodInfo methodInfo) : base(methodInfo) { }
public MyTypeInformation(FieldInfo fieldInfo, bool isInput) : base(fieldInfo, isInput) { }

public override Type ConstructGraphType()
{
var type = GraphType;
if (type != null)
{
if (!IsNullable)
type = typeof(NonNullGraphType<>).MakeGenericType(type);
}
else
{
type = GetGraphTypeFromType(Type, IsNullable, IsInputType ? TypeMappingMode.InputType : TypeMappingMode.OutputType);
}
if (IsList)
{
type = typeof(ListGraphType<>).MakeGenericType(type);
if (!ListIsNullable)
type = typeof(NonNullGraphType<>).MakeGenericType(type);
}
return type;
}

/// <summary>
/// Gets the graph type for the indicated type.
/// </summary>
/// <param name="type">The type for which a graph type is desired.</param>
/// <param name="isNullable">if set to <c>false</c> if the type explicitly non-nullable.</param>
/// <param name="mode">Mode to use when mapping CLR type to GraphType.</param>
/// <returns>A Type object representing a GraphType that matches the indicated type.</returns>
/// <remarks>This can handle arrays, lists and other collections implementing IEnumerable.</remarks>
private static Type GetGraphTypeFromType(Type type, bool isNullable = false, TypeMappingMode mode = TypeMappingMode.UseBuiltInScalarMappings)
{
while (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDataLoaderResult<>))
{
type = type.GetGenericArguments()[0];
}

if (type == typeof(IDataLoaderResult))
{
type = typeof(object);
}

if (typeof(Task).IsAssignableFrom(type))
throw new ArgumentOutOfRangeException(nameof(type), "Task types cannot be coerced to a graph type; please unwrap the task type before calling this method.");

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
type = type.GetGenericArguments()[0];
if (!isNullable)
{
throw new ArgumentOutOfRangeException(nameof(isNullable),
$"Explicitly nullable type: Nullable<{type.Name}> cannot be coerced to a non nullable GraphQL type.");
}
}

Type? graphType = null;

if (type.IsArray)
{
var clrElementType = type.GetElementType()!;
var elementType = GetGraphTypeFromType(clrElementType, IsNullableType(clrElementType), mode); // isNullable from elementType, not from parent array
graphType = typeof(ListGraphType<>).MakeGenericType(elementType);
}
else if (TryGetEnumerableElementType(type, out var clrElementType))
{
var elementType = GetGraphTypeFromType(clrElementType!, IsNullableType(clrElementType!), mode); // isNullable from elementType, not from parent container
graphType = typeof(ListGraphType<>).MakeGenericType(elementType);
}
else
{
#pragma warning disable CS0618 // Type or member is obsolete
var attr = type.GetCustomAttribute<GraphQLMetadataAttribute>();
if (attr != null)
{
if (mode == TypeMappingMode.InputType)
graphType = attr.InputType;
else if (mode == TypeMappingMode.OutputType)
graphType = attr.OutputType;
else if (attr.InputType == attr.OutputType) // scalar
graphType = attr.InputType;
}
#pragma warning restore CS0618 // Type or member is obsolete

if (mode == TypeMappingMode.InputType)
{
var inputAttr = type.GetCustomAttribute<InputTypeAttribute>();
if (inputAttr != null)
graphType = inputAttr.InputType;
}
else if (mode == TypeMappingMode.OutputType)
{
var outputAttr = type.GetCustomAttribute<OutputTypeAttribute>();
if (outputAttr != null)
graphType = outputAttr.OutputType;
}

if (graphType == null)
{
if (mode == TypeMappingMode.UseBuiltInScalarMappings)
{
if (!SchemaTypes.BuiltInScalarMappings.TryGetValue(type, out graphType))
{
if (type.IsEnum)
{
graphType = typeof(EnumerationGraphType<>).MakeGenericType(type);
}
else
{
throw new ArgumentOutOfRangeException(nameof(type), $"The CLR type '{type.FullName}' cannot be coerced effectively to a GraphQL type.");
}
}
}
else
{
graphType = (mode == TypeMappingMode.OutputType ? typeof(GraphQLClrOutputTypeReference<>) : typeof(GraphQLClrInputTypeReference<>)).MakeGenericType(type);
}
}
}

if (!isNullable)
{
graphType = typeof(NonNullGraphType<>).MakeGenericType(graphType);
}

return graphType;

//TODO: rewrite nullability condition in v5
static bool IsNullableType(Type type) => !type.IsValueType || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}

private static readonly Type[] _enumerableTypes = new[] {
typeof(IEnumerable<>),
typeof(IList<>),
typeof(List<>),
typeof(ICollection<>),
typeof(IReadOnlyCollection<>),
typeof(IReadOnlyList<>),
typeof(HashSet<>),
typeof(ISet<>),
};

private static bool TryGetEnumerableElementType(Type type, out Type? elementType)
{
if (type == typeof(IEnumerable))
{
elementType = typeof(object);
return true;
}

if (!type.IsGenericType || !_enumerableTypes.Contains(type.GetGenericTypeDefinition()))
{
elementType = null;
return false;
}

elementType = type.GetGenericArguments()[0];
return true;
}
}
#endregion
}
}

0 comments on commit 480dda0

Please sign in to comment.