Skip to content

Rewrite expression parser #53

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

Merged
merged 8 commits into from
Mar 19, 2025
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
@@ -0,0 +1,8 @@
namespace Linq2GraphQL.Client;

[AttributeUsage(AttributeTargets.Parameter)]
public class GraphQLArgumentAttribute(string graphQLName, string graphQLType) : Attribute
{
public string GraphQLType { get; private set; } = graphQLType;
public string GraphQLName { get; private set; } = graphQLName;
}
14 changes: 14 additions & 0 deletions src/Linq2GraphQL.Client/Attributes/GraphQLMemberAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Text.Json.Serialization;

namespace Linq2GraphQL.Client;

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public class GraphQLMemberAttribute : Attribute
{
public GraphQLMemberAttribute(string graphQLName)
{
GraphQLName = graphQLName;
}

public string GraphQLName { get; private set; }
}
2 changes: 2 additions & 0 deletions src/Linq2GraphQL.Client/Common/ICursorPaging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Linq2GraphQL.Client.Common
{
public interface ICursorPaging
{
[GraphQLMember("pageInfo")]
[JsonPropertyName("pageInfo")]
public PageInfo PageInfo { get; set; }

}
Expand Down
7 changes: 7 additions & 0 deletions src/Linq2GraphQL.Client/Common/IPageInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@ namespace Linq2GraphQL.Client.Common
{
public class PageInfo
{
[GraphQLMember("hasNextPage")]
[JsonPropertyName("hasNextPage")]
public bool HasNextPage { get; set; }

[GraphQLMember("hasPreviousPage")]
[JsonPropertyName("hasPreviousPage")]
public bool HasPreviousPage { get; set; }

[GraphQLMember("startCursor")]
[JsonPropertyName("startCursor")]
public string StartCursor { get; set; }

[GraphQLMember("endCursor")]
[JsonPropertyName("endCursor")]
public string EndCursor { get; set; }

Expand Down
3 changes: 0 additions & 3 deletions src/Linq2GraphQL.Client/Converters/CustomScalarConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
namespace Linq2GraphQL.Client
{




public class CustomScalarConverter<TScalar> : JsonConverter<TScalar>
where TScalar : CustomScalar, new()
{
Expand Down
11 changes: 11 additions & 0 deletions src/Linq2GraphQL.Client/GraphBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ public GraphBase(GraphClient client, string name, OperationType operationType, L

public QueryNode QueryNode { get; }


/// <summary>
/// Include top node
/// </summary>
/// <returns></returns>
public TGraph Include()
{
QueryNode.IncludePrimitive = true;
return (TGraph)(object)this;
}

public TGraph Include<TProperty>(Expression<Func<T, TProperty>> path)
{
Utilities.ParseExpression(path, QueryNode);
Expand Down
4 changes: 3 additions & 1 deletion src/Linq2GraphQL.Client/GraphClient.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Linq2GraphQL.Client.Converters;
using Linq2GraphQL.Client.Schema;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -25,7 +26,7 @@ public GraphClient(HttpClient httpClient, IOptions<GraphClientOptions> options,
SerializerOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { }
Converters = { },
};

SubscriptionUrl = GetSubscriptionUrl();
Expand All @@ -35,6 +36,7 @@ public GraphClient(HttpClient httpClient, IOptions<GraphClientOptions> options,
public SubscriptionProtocol SubscriptionProtocol => options.Value.SubscriptionProtocol;
public HttpClient HttpClient { get; }
public JsonSerializerOptions SerializerOptions { get; }


private string GetSubscriptionUrl()
{
Expand Down
34 changes: 14 additions & 20 deletions src/Linq2GraphQL.Client/QueryNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class QueryNode

public QueryNode(MemberInfo member, string name = null, List<ArgumentValue> arguments = null, bool interfaceProperty = false, bool topLevel = false)
{
Name = name ?? member.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? member.Name.ToCamelCase();
Name = name ?? member.GetCustomAttribute<GraphQLMemberAttribute>()?.GraphQLName ?? member.Name.ToCamelCase();
Member = member;
Arguments = arguments ?? new List<ArgumentValue>();
underlyingMemberType = member.GetUnderlyingType();
Expand Down Expand Up @@ -62,17 +62,6 @@ private static bool MustHaveChildren(Type type)
!type.IsListOfPrimitiveTypeOrString();
}

public void SetAddPrimitiveChildren()
{
if (!ChildNodes.Any())
{
IncludePrimitive = true;
}
foreach (var childNode in ChildNodes)
{
childNode.SetAddPrimitiveChildren();
}
}

public void AddChildNode(MemberInfo member, string name = null)
{
Expand All @@ -82,15 +71,15 @@ public void AddChildNode(MemberInfo member, string name = null)
public int Level => Parent?.Level + 1 ?? 1;
public int Leaf { get; internal set; } = 1;

public void AddChildNode(QueryNode childNode)
public QueryNode AddChildNode(QueryNode childNode)
{
var currentNode = ChildNodes.FirstOrDefault(e => e.Name == childNode.Name && e.argumentHashCodeId == childNode.argumentHashCodeId);
if (currentNode == null)
{
childNode.Parent = this;
childNode.Leaf = ChildNodes.Count + 1;
ChildNodes.Add(childNode);
return;
return childNode;
}
else if (childNode.IncludePrimitive)
{
Expand All @@ -101,6 +90,9 @@ public void AddChildNode(QueryNode childNode)
{
currentNode.AddChildNode(child);
}

return currentNode;

}

public void SetArgumentValue(string graphName, object value)
Expand All @@ -127,23 +119,25 @@ public void AddPrimitiveChildren(bool recursive, GraphQLSchema schema)
var typeOrListType = underlyingMemberType.GetTypeOrListType();
foreach (var propertyInfo in typeOrListType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (propertyInfo.GetCustomAttribute<GraphShadowPropertyAttribute>() != null)

if (!propertyInfo.PropertyType.IsValueTypeOrString())
{
continue;
}

if (!propertyInfo.PropertyType.IsValueTypeOrString() || propertyInfo.GetCustomAttribute<GraphShadowPropertyAttribute>() != null)

var memberAttribute = propertyInfo.GetCustomAttribute<GraphQLMemberAttribute>();
if (memberAttribute == null)
{
continue;
}


if (schema != null)
{
var name = propertyInfo.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ??
Member.Name.ToCamelCase();
if (schema.TypePropertyExists(typeOrListType.Name, name))
if (schema.TypePropertyExists(typeOrListType.Name, memberAttribute.GraphQLName))
{
AddChildNode(propertyInfo, name);
AddChildNode(propertyInfo, memberAttribute.GraphQLName);
}
else
{
Expand Down
167 changes: 7 additions & 160 deletions src/Linq2GraphQL.Client/Utilities.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Metadata.Ecma335;

using Linq2GraphQL.Client.Visitors;
using System.Linq.Expressions;
namespace Linq2GraphQL.Client;

public static class Utilities
Expand All @@ -24,164 +22,13 @@ public static string GetArgumentsId(IEnumerable<object> objects)
}
}

private static bool IsSelectOrSelectMany(this MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Arguments.Count != 2)
{
return false;
}

;
var methodName = methodCallExpression.Method.Name;
return (methodName == "Select" || methodName == "SelectMany");
}

public static void ParseExpression(Expression body, QueryNode parent)
{
var node = new QueryNode(parent.Member);
ParseExpressionInternal(body, node);
node.SetAddPrimitiveChildren();

foreach (var childNode in node.ChildNodes)
{
parent.AddChildNode(childNode);
}
}

private static void ParseExpressionInternal(Expression body, QueryNode parent)
{
if (body.NodeType == ExpressionType.MemberInit)
{
var exp = (MemberInitExpression)body;
foreach (var binding in exp.Bindings.Where(e => e.BindingType == MemberBindingType.Assignment)
.Cast<MemberAssignment>())
{
ParseExpressionInternal(binding.Expression, parent);
}
}

switch (body)
{
case LambdaExpression lambdaExpression:
ParseExpressionInternal(lambdaExpression.Body, parent);
break;

case MemberExpression memberExpression:
var (parentNode, _) = GetMemberQueryNode(memberExpression);
parent.AddChildNode(parentNode);
break;

case MethodCallExpression methodCallExp:
ParseMethodCallExpression(parent, methodCallExp);
break;

case NewExpression newExpression:
foreach (var argument in newExpression.Arguments)
{
ParseExpression(argument, parent);
}

break;
}
}

private static void ParseMethodCallExpression(QueryNode parent, MethodCallExpression methodCallExp)
{
var graphInterfaceAttribute = methodCallExp.Method.GetCustomAttribute<GraphInterfaceAttribute>();
if (graphInterfaceAttribute != null)
{
var queryNode = new QueryNode(methodCallExp.Method, methodCallExp.Method.Name, null, true);
parent.AddChildNode(queryNode);
return;
}

var graphMethodAttribute = methodCallExp.Method.GetCustomAttribute<GraphMethodAttribute>();
if (graphMethodAttribute != null)
{
var arguments = new List<ArgumentValue>();

var i = 0;
foreach (var parameter in methodCallExp.Method.GetParameters())
{
var graphAttribute = parameter.GetCustomAttribute<GraphArgumentAttribute>();
if (graphAttribute != null)
{
var arg = methodCallExp.Arguments[i];
ConstantExpression argConstant;
if (arg.NodeType == ExpressionType.Convert)
{
var unaryExpression = (UnaryExpression)arg;
argConstant = (ConstantExpression)unaryExpression.Operand;
}
else
{
argConstant = (ConstantExpression)arg;
}

arguments.Add(new ArgumentValue(parameter.Name, graphAttribute.GraphType,
argConstant.Value));
}

i++;
}

var queryNode = new QueryNode(methodCallExp.Method, graphMethodAttribute.GraphName, arguments);
parent.AddChildNode(queryNode);
}
else if (methodCallExp.IsSelectOrSelectMany())
{
if (methodCallExp.Arguments[0] is MemberExpression memberExpr)
{
var (ParentNode, LastNode) = GetMemberQueryNode(memberExpr);
ParseExpressionInternal(methodCallExp.Arguments[1], LastNode);
parent.AddChildNode(ParentNode);
}
else
{
ParseExpressionInternal(methodCallExp.Arguments[1], parent);
}
}
}

private static (QueryNode ParentNode, QueryNode LastNode) GetMemberQueryNode(Expression expression)
{
var members = GetMembers(expression);
if (members == null) return (null, null);

members.Reverse();

QueryNode parentNode = null;
QueryNode currentNode = null;

foreach (var member in members)
{
var newNode = new QueryNode(member);
if (parentNode == null)
{
parentNode = newNode;
}
else
{
currentNode.AddChildNode(newNode);
}

currentNode = newNode;
}

return (parentNode, currentNode);
var parameterVisitor = new ParameterVisitor(new MemberNode(null, null));
var topNode = parameterVisitor.ParseExpression(body);

topNode.PopulateChildQueryNodes(parent);

}


private static List<MemberInfo> GetMembers(Expression expression)
{
var members = new List<MemberInfo>();
if (expression.NodeType == ExpressionType.MemberAccess)
{
var memberExpression = (MemberExpression)expression;
members.Add(memberExpression.Member);
members.AddRange(GetMembers(memberExpression.Expression));
}

return members;
}
}
Loading