graphql-dotnet: IntrospectionQuery returns error "A query is required"
I’m getting this error returned during an introspection query:
"GraphQL.ExecutionError: A query is required. ---> GraphQL.ExecutionError: A query is required.\r\n at GraphQL.DocumentExecuter.ValidateOptions(ExecutionOptions options)\r\n at GraphQL.DocumentExecuter.<ExecuteAsync>d__8.MoveNext()\r\n --- End of inner exception stack trace ---"
This could definitely be caused by something I’m doing wrong but it could also be a bug with GraphQL-Net, but I’m really not even sure how to approach identifying which of those it is because I’m not sure how GraphQL-Net handles the introspection operation.
This is the query being requested:
query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } }}fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef }}fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue}fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } }}
This is my middleware:
public class GraphQLMiddleware
{
private readonly RequestDelegate _next;
private readonly GraphQLSettings _settings;
private readonly IDocumentExecuter _executer;
private readonly IDocumentWriter _writer;
private readonly DataLoaderDocumentListener _listener;
private readonly ISchema _schema;
public GraphQLMiddleware(
RequestDelegate next,
GraphQLSettings settings,
IDocumentExecuter executer,
IDocumentWriter writer,
DataLoaderDocumentListener listener,
ISchema schema)
{
_next = next;
_settings = settings;
_executer = executer;
_writer = writer;
_listener = listener;
_schema = schema;
}
public async Task Invoke(HttpContext context)
{
context.CheckNotNull<HttpContext>();
if (!IsGraphQLRequest(context))
{
await _next(context);
return;
}
await ExecuteAsync(context);
}
private bool IsGraphQLRequest(HttpContext context)
{
return context.CheckNotNull<HttpContext>().Request.Path.StartsWithSegments(_settings.Path) && string.Equals(context.Request.Method, "POST", StringComparison.OrdinalIgnoreCase);
}
private async Task ExecuteAsync(HttpContext context)
{
var request = Deserialize<GraphQLRequest>(context.Request.Body);
var result = await GetExecutionResultAsync(context, request);
await WriteResponseAsync(context, result);
}
private async Task<ExecutionResult> GetExecutionResultAsync(HttpContext context, GraphQLRequest request)
{
return await _executer.ExecuteAsync(_ =>
{
_.Schema = _schema;
_.ExposeExceptions = true;
_.Query = request?.Query;
_.OperationName = request?.OperationName;
_.Inputs = request?.Variables.ToInputs();
_.UserContext = _settings.BuildUserContext?.Invoke(context).GetAwaiter().GetResult();
_.ValidationRules = DocumentValidator.CoreRules().Concat(new IValidationRule[]
{
new AuthValidationRule()
});
_.EnableMetrics = _settings.EnableMetrics;
_.Listeners.Add(_listener);
if (_settings.EnableMetrics)
{
_.FieldMiddleware.Use<InstrumentFieldsMiddleware>();
}
});
}
private async Task WriteResponseAsync(HttpContext context, ExecutionResult result)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = result.Errors?.Any() == true ? (int)HttpStatusCode.BadRequest : (int)HttpStatusCode.OK;
await _writer.WriteAsync(context.Response.Body, result);
}
public static T Deserialize<T>(Stream s)
{
using var reader = new StreamReader(s);
using var jsonReader = new JsonTextReader(reader);
var ser = new JsonSerializer();
return ser.Deserialize<T>(jsonReader);
}
}
Here is my Startup.cs:
public class Startup
{
public static void ConfigureServices(IServiceCollection services)
{
services.AddGraphQLAuth();
services.AddDependencyInjection();
RegisterTypes.AddTypeConversions();
services.Configure<IISServerOptions>(options =>
{
options.AutomaticAuthentication = false;
});
}
public static void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseDeveloperExceptionPage();
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
//app.UseHsts();
}
app.UseCorsMiddleware();
app.UseGraphQLWithAuth();
app.UseGraphQLPlayground(new GraphQLPlaygroundOptions { Path = "/ui/playground" });
}
}
and here is my “UseGraphQLWithAuth.cs”
public static class GraphQLAuthorizationService
{
public static void AddGraphQLAuth(this IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>();
services.TryAddTransient<IValidationRule, AuthorizationValidationRule>();
}
public static void UseGraphQLWithAuth(this IApplicationBuilder app)
{
var settings = new GraphQLSettings
{
BuildUserContext = async ctx =>
{
var userContext = new GraphQLUserContext
{
User = ctx.User,
};
return await System.Threading.Tasks.Task.FromResult(userContext);
},
EnableMetrics = true
};
var rules = app.CheckNotNull<IApplicationBuilder>().ApplicationServices.GetServices<IValidationRule>();
settings.ValidationRules.AddRange(rules);
app.UseMiddleware<GraphQLMiddleware>(settings);
}
}
If it’s not likely to be a bug and is something I’m doing wrong, I would greatly appreciate someone pointing me the right direction in terms of how the IntrospectionQuery is supposed to be resolved & what code I could have introduced to potentially interfere with it working correctly.
I do have a single custom validation rule, but when I test the introspection query being sent from Postman, GraphQL playground or even just executing the above introspection query, it never actually hits my validation rule before returning the above error.
Thanks in advance.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 22 (11 by maintainers)
In your example,
SaleOrdershould be anObjectGraphTypeandFilterInputSalesOrdershould be anInputObjectGraphType. All properties ofFilterInputerSalesOrdershould either be scalars or otherInputObjectGraphType. You can never mix input objects with just objects and vise versa. Scalars, including enumerations, can be used in both.What package version do you use? Try the latest 3.0.0-preview.
That error says to me that you are using a non-input type somewhere where you shouldn’t.
You can print your Schema using
SchemaPrinterto view what is actually being constructed and look for problems.My only guess would be to make sure that the url configured is the correct one on startup (presumably it is, it defaults to just
/graphqlonGraphQLPlaygroundOptions.GraphQLEndpoint). You may not get intellisense if the initial introspection query fails for whatever reason. I would check to see if the initial http request is successful or not, since that is what should also be displaying the Schema.https://github.com/graphql-dotnet/server/blob/60234479fee2591eb6cb24bea6e1427aab807e4b/src/Ui.Playground/GraphQLPlaygroundOptions.cs#L19
As spec says all graphql types MUST have unique names: https://graphql.github.io/graphql-spec/June2018/#sec-Schema
In a number of recent commits, we have added several checks that make sure of this. Previously, you could create such a schema in which the types had the same name (or even create a type without a name). Sometimes this led to strange behavior, sometimes to runtime errors. Now it is checked at the stage of creating the schema. Each graphtype gets a default name in the constructor: https://github.com/graphql-dotnet/graphql-dotnet/blob/aed5298a435012d41d42dd0a9accebdb3cff1de3/src/GraphQL/Types/GraphType.cs#L18 You can optionally set your name, or you can leave the one that was set in the constructor. For generic types, there will obviously be a name conflict -
FilterInput_2in your case. In this case, you have to look for workarounds, for example, to inherit. The second option is to change the default name so that it displays a name with an even generic parameters. I deliberately did not do this until the first necessity. In this case, you will get a graphtype with a name like this:FilterInput_Of_SalesOrderDetailEnum_And_SalesOrderDetailI hope that you understand why I did not do this right away. In addition, the users of your api will see these names. I do not think this will be a positive experience for them. By the way, I even wondered - how did your api look like before? What type names did it provide to clients?