ZeroQL
ZeroQL copied to clipboard
Can't wrap the Query calls in another method.
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
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);
}
}
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.
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.
Hmm.. Potentially the interface can be even a better way to implement it. I will have a look.