ZeroQL icon indicating copy to clipboard operation
ZeroQL copied to clipboard

Can't wrap the Query calls in another method.

Open lukemurray opened this issue 10 months ago • 4 comments

Describe the bug

Can't wrap the Query calls in another method.

How to Reproduce

We're trying to replace an existing implementation and wanted to limit the changes across the code base. So we have the following

public async Task<GraphQLResult<TQueryResult>> QueryAsync<TQueryResult>(string operationName, Func<Query, TQueryResult> query)
        {
            var gqlClient = // .. make generated client with our IHttpHandler
            // do other things we need
            var res = await gqlClient.Query(operationName, query);
            return res;
        }

The await gqlClient.Query(operationName, query); gives the following error

Source generator failed unexpectedly with exception message:
Sequence contains no matching element
   at System.Linq.ThrowHelper.ThrowNoMatchException()
   at ZeroQL.SourceGenerators.Resolver.Context.GraphQLLambdaLikeContextResolver.Resolve(InvocationExpressionSyntax invocation, SemanticModel semanticModel, CancellationToken cancellationToken)
   at ZeroQL.SourceGenerators.Generator.GraphQLLambdaIncrementalSourceGenerator.GenerateFile(SourceProductionContext context, InvocationExpressionSyntax invocation, SemanticModel semanticModel, HashSet`1 processed)
   at ZeroQL.SourceGenerators.Generator.GraphQLLambdaIncrementalSourceGenerator.<>c__DisplayClass1_1.<GenerateSource>b__0()
   at ZeroQL.SourceGenerators.Utils.ErrorWrapper(SourceProductionContext context, CSharpSyntaxNode location, Action action)
Roslyn(ZQL0001)

Expected behavior

Clearly a Source Generator issue, but I'm not sure if it is a limitation of Source Generators or if it could be supported. If it is a limitation of Source Generators then you can close this. Otherwise it would be good to support this.

Environment (please complete the following information):

  • Nuget package version - 6.0.0
  • IDE: VS Code on Mac OS
  • .Net Version - 6

lukemurray avatar Aug 15 '23 11:08 lukemurray

I tried something similar with the QueryInfoProvider. But you have to fetch the CallerArgumentExpression in the wrapper method else it will not work:

    public static void Query<TQuery>(Func<TQuery, object> query, [CallerArgumentExpression("query")] string queryKey = "")
    {
        var queryInfo = QueryInfoProvider.Materialize<TQuery>(query, queryKey);
    }

Also the queryKey is never set at runtime, it's important at compile time as the SourceGenerator from ZeroQL analyzes to build the query string and the query store. And retrieve the correct query string at runtime.

Edit: But it didn't worked out nicely. So i resorted to create an extension method which returned custom client with the IHttpHandler

public static class GraphQlClientExtensions
{
    // ReSharper disable once InconsistentNaming
    public static GraphQLClient GraphQLClient(this IClient client, string projectKey)
    {
        return new GraphQLClient(client.ToHandler(projectKey));
    }

    private static IHttpHandler ToHandler(this IClient client, string projectKey)
    {
        return new ClientHandler(client, projectKey);
    }
}

internal class ClientHandler : IHttpHandler
{
    private readonly IClient _client;
    private readonly Uri _graphQlUri;

    public ClientHandler(IClient client, string projectKey)
    {
        _client = client;
        _graphQlUri = new Uri($"/{projectKey}/graphql", UriKind.Relative);
    }

    public void Dispose()
    {
    }

    public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.RequestUri = _graphQlUri;
        return await _client.SendAsAsync(request, cancellationToken);
    }
}

jenschude avatar Aug 16 '23 11:08 jenschude

Theoretically, there is a way to make it work. It would require changes in analyzer and it would not look nice. The QueryAsync would be changed like that:

       public async Task<GraphQLResult<TQueryResult>> QueryAsync<TQueryResult>(string operationName, [GraphQLLambda]Func<Query, TQueryResult> query, [CallerArgumentExpression(nameof(query))] string queryKey = null!)
        {
            var gqlClient = // .. make generated client with our IHttpHandler
            // do other things we need
            var res = await gqlClient.Query(operationName, query, queryKey);
            return res;
        }

and I have a feeling that such implementation would definitely get some sort of unknown WTF.

byme8 avatar Aug 16 '23 11:08 byme8

It would already help if there is a way that the analyzers capture classes implementing an interface provided by ZeroQL so libraries could implement it and read the query body from the QueryInfoProvider instead of using the generated ZeroQL client. Would have also the nice side effect to get the tooltips with the compiled query.

This would also ensure that the necessary functionality is supported like variable capturing etc.

jenschude avatar Aug 16 '23 11:08 jenschude

Hmm.. Potentially the interface can be even a better way to implement it. I will have a look.

byme8 avatar Aug 16 '23 11:08 byme8