From 14f55ca31bf4767b02343ace9c4de0e29bac84c0 Mon Sep 17 00:00:00 2001 From: Giorgi Dalakishvili Date: Sun, 2 Apr 2017 21:20:14 +0400 Subject: [PATCH] Modify Include method so that it accepts QueryExtensions methods. Implemented task 2 from issue #2 --- GraphQLinq/ExtensionsUtils.cs | 90 +++++++++++++++++ GraphQLinq/GraphQLinq.csproj | 1 + GraphQLinq/GraphQuery.cs | 171 +++++++++++++------------------- GraphQLinq/GraphQueryBuilder.cs | 21 ++-- 4 files changed, 172 insertions(+), 111 deletions(-) create mode 100644 GraphQLinq/ExtensionsUtils.cs diff --git a/GraphQLinq/ExtensionsUtils.cs b/GraphQLinq/ExtensionsUtils.cs new file mode 100644 index 0000000..955b05b --- /dev/null +++ b/GraphQLinq/ExtensionsUtils.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace GraphQLinq +{ + static class ExtensionsUtils + { + internal static bool IsPrimitiveOrString(this Type type) + { + return type.IsPrimitive || type == typeof(string); + } + + internal static bool IsList(this Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>); + } + + internal static bool HasNestedProperties(this Type type) + { + var trueType = GetTypeOrListType(type); + + return !IsPrimitiveOrString(trueType); + } + + internal static Type GetTypeOrListType(this Type type) + { + if (type.IsList()) + { + var genericArguments = type.GetGenericArguments(); + + return genericArguments[0].GetTypeOrListType(); + } + + return type; + } + + internal static Expression RemoveConvert(this Expression expression) + { + while ((expression != null) + && (expression.NodeType == ExpressionType.Convert + || expression.NodeType == ExpressionType.ConvertChecked)) + { + expression = RemoveConvert(((UnaryExpression)expression).Operand); + } + + return expression; + } + + internal static string ToCamelCase(this string input) + { + if (char.IsLower(input[0])) + { + return input; + } + return input.Substring(0, 1).ToLower() + input.Substring(1); + } + + internal static string ToGraphQlType(this Type type) + { + if (type == typeof(bool)) + { + return "Boolean"; + } + + if (type == typeof(int)) + { + return "Int"; + } + + if (type == typeof(string)) + { + return "String!"; + } + + if (type == typeof(float)) + { + return "Float"; + } + + if (type.IsList()) + { + var listType = type.GetTypeOrListType(); + return "[" + ToGraphQlType(listType) + "]"; + } + + return type.Name; + } + } +} \ No newline at end of file diff --git a/GraphQLinq/GraphQLinq.csproj b/GraphQLinq/GraphQLinq.csproj index 2c83cd5..fbbf163 100644 --- a/GraphQLinq/GraphQLinq.csproj +++ b/GraphQLinq/GraphQLinq.csproj @@ -44,6 +44,7 @@ + diff --git a/GraphQLinq/GraphQuery.cs b/GraphQLinq/GraphQuery.cs index ea7d069..d9444ea 100644 --- a/GraphQLinq/GraphQuery.cs +++ b/GraphQLinq/GraphQuery.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; namespace GraphQLinq { @@ -14,7 +15,7 @@ public abstract class GraphQuery internal string QueryName { get; } internal LambdaExpression Selector { get; private set; } - internal List Includes { get; private set; } = new List(); + internal List Includes { get; private set; } = new List(); internal Dictionary Arguments { get; set; } = new Dictionary(); internal GraphQuery(GraphContext graphContext, string queryName) @@ -48,61 +49,94 @@ protected GraphQuery Clone() return instance; } - protected static bool TryParsePath(Expression expression, out string path) + internal static IncludeDetails ParseIncludePath(Expression expression) { - path = null; + string path = null; var withoutConvert = expression.RemoveConvert(); // Removes boxing var memberExpression = withoutConvert as MemberExpression; var callExpression = withoutConvert as MethodCallExpression; if (memberExpression != null) { - var thisPart = memberExpression.Member.Name; - string parentPart; - if (!TryParsePath(memberExpression.Expression, out parentPart)) + var parentPath = ParseIncludePath(memberExpression.Expression); + if (parentPath == null) { - return false; + return null; } - path = parentPart == null ? thisPart : (parentPart + "." + thisPart); + + var thisPart = memberExpression.Member.Name; + path = parentPath.Path == null ? thisPart : (parentPath.Path + "." + thisPart); } else if (callExpression != null) { - if (callExpression.Method.Name == "Select" - && callExpression.Arguments.Count == 2) + if (callExpression.Method.Name == "Select" && callExpression.Arguments.Count == 2) { - string parentPart; - if (!TryParsePath(callExpression.Arguments[0], out parentPart)) + var parentPath = ParseIncludePath(callExpression.Arguments[0]); + if (parentPath == null) { - return false; + return null; } - if (parentPart != null) + + if (parentPath.Path != null) { var subExpression = callExpression.Arguments[1] as LambdaExpression; if (subExpression != null) { - string thisPart; - if (!TryParsePath(subExpression.Body, out thisPart)) + var thisPath = ParseIncludePath(subExpression.Body); + if (thisPath == null) { - return false; + return null; } - if (thisPart != null) + + if (thisPath.Path != null) { - path = parentPart + "." + thisPart; - return true; + path = parentPath.Path + "." + thisPath.Path; + var result = new IncludeDetails { Path = path}; + + result.MethodIncludes.AddRange(parentPath.MethodIncludes); + result.MethodIncludes.AddRange(thisPath.MethodIncludes); + + return result; } } } } - return false; + if (callExpression.Method.DeclaringType?.Name == "QueryExtensions") + { + var parentPath = ParseIncludePath(callExpression.Arguments[0]); + if (parentPath == null) + { + return null; + } + + path = parentPath.Path == null ? callExpression.Method.Name : parentPath.Path + "." + callExpression.Method.Name; + + var arguments = callExpression.Arguments.Zip(callExpression.Method.GetParameters(), (argument, parameter) => new + { + Argument = argument, + parameter.Name + }).Skip(1).ToDictionary(arg => arg.Name, arg => (arg.Argument as ConstantExpression).Value); + + var result = new IncludeDetails { Path = path }; + result.MethodIncludes.Add(new IncludeMethodDetails + { + Method = callExpression.Method, + Parameters = arguments + }); + result.MethodIncludes.AddRange(parentPath.MethodIncludes); + return result; + } + + return null; } - return true; + return new IncludeDetails { Path = path }; } protected GraphQuery BuildInclude(Expression> path) { - string include; - if (!TryParsePath(path.Body, out include) || include == null) + var include = ParseIncludePath(path.Body); + if (include?.Path == null) { throw new ArgumentException("Invalid Include Path Expression", nameof(path)); } @@ -130,7 +164,7 @@ internal IEnumerator BuildEnumerator(QueryType queryType) { var query = lazyQuery.Value; - var mapper = (Func) Selector?.Compile(); + var mapper = (Func)Selector?.Compile(); return new GraphQueryEnumerator(query.FullQuery, context.BaseUrl, context.Authorization, queryType, mapper); } @@ -209,87 +243,20 @@ internal enum QueryType Collection } - static class ExtensionsUtils + class IncludeDetails { - internal static bool IsPrimitiveOrString(this Type type) - { - return type.IsPrimitive || type == typeof(string); - } - - internal static bool IsList(this Type type) - { - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>); - } - - internal static bool HasNestedProperties(this Type type) - { - var trueType = GetTypeOrListType(type); - - return !IsPrimitiveOrString(trueType); - } - - internal static Type GetTypeOrListType(this Type type) - { - if (type.IsList()) - { - var genericArguments = type.GetGenericArguments(); - - return genericArguments[0].GetTypeOrListType(); - } - - return type; - } - - internal static Expression RemoveConvert(this Expression expression) - { - while ((expression != null) - && (expression.NodeType == ExpressionType.Convert - || expression.NodeType == ExpressionType.ConvertChecked)) - { - expression = RemoveConvert(((UnaryExpression)expression).Operand); - } - - return expression; - } + public string Path { get; set; } + public List MethodIncludes { get; } = new List(); + } - internal static string ToCamelCase(this string input) - { - if (char.IsLower(input[0])) - { - return input; - } - return input.Substring(0, 1).ToLower() + input.Substring(1); - } + class IncludeMethodDetails + { + public MethodInfo Method { get; set; } + public Dictionary Parameters { get; set; } - internal static string ToGraphQlType(this Type type) + public override string ToString() { - if (type == typeof(bool)) - { - return "Boolean"; - } - - if (type == typeof(int)) - { - return "Int"; - } - - if (type == typeof(string)) - { - return "String!"; - } - - if (type == typeof(float)) - { - return "Float"; - } - - if (type.IsList()) - { - var listType = type.GetTypeOrListType(); - return "[" + ToGraphQlType(listType) + "]"; - } - - return type.Name; + return Method.Name; } } } \ No newline at end of file diff --git a/GraphQLinq/GraphQueryBuilder.cs b/GraphQLinq/GraphQueryBuilder.cs index b6c1f23..7e63320 100644 --- a/GraphQLinq/GraphQueryBuilder.cs +++ b/GraphQLinq/GraphQueryBuilder.cs @@ -13,7 +13,7 @@ class GraphQueryBuilder private const string QueryTemplate = @"query {0} {{ {1}: {2} {3} {{ {4} }}}}"; internal const string ResultAlias = "result"; - public GraphQLQuery BuildQuery(GraphQuery graphQuery, List includes) + public GraphQLQuery BuildQuery(GraphQuery graphQuery, List includes) { var selectClause = ""; @@ -105,7 +105,7 @@ private static string BuildSelectClauseForType(Type targetType, int depth = 1) return selectClause; } - private static string BuildSelectClauseForType(Type targetType, IEnumerable includes) + private static string BuildSelectClauseForType(Type targetType, IEnumerable includes) { var selectClause = BuildSelectClauseForType(targetType); @@ -118,8 +118,9 @@ private static string BuildSelectClauseForType(Type targetType, IEnumerable= 0 ? include.Substring(dotIndex + 1) : ""; - var currentPropertyName = dotIndex >= 0 ? include.Substring(0, dotIndex) : include; + var currentIncludeName = dotIndex >= 0 ? include.Substring(0, dotIndex) : include; - var propertyType = targetType.GetProperty(currentPropertyName).PropertyType.GetTypeOrListType(); + var propertyInfo = targetType.GetProperty(currentIncludeName); + var propertyType = propertyInfo.PropertyType.GetTypeOrListType(); if (propertyType.IsPrimitiveOrString()) { - return leftPadding + currentPropertyName.ToCamelCase(); + return leftPadding + currentIncludeName.ToCamelCase(); } - var fieldsFromInclude = BuildSelectClauseForInclude(propertyType, restOfTheIncludePath, depth + 1); - fieldsFromInclude = $"{leftPadding}{currentPropertyName.ToCamelCase()} {{{Environment.NewLine}{fieldsFromInclude}{Environment.NewLine}{leftPadding}}}"; + var restOfTheInclude = new IncludeDetails {Path = dotIndex >= 0 ? include.Substring(dotIndex + 1) : "" }; + + var fieldsFromInclude = BuildSelectClauseForInclude(propertyType, restOfTheInclude, depth + 1); + fieldsFromInclude = $"{leftPadding}{currentIncludeName.ToCamelCase()} {{{Environment.NewLine}{fieldsFromInclude}{Environment.NewLine}{leftPadding}}}"; return fieldsFromInclude; } }