execute static method
Executes document, returning the spec-shaped {data} or {errors}.
A request-level failure (parse error, unknown operation) yields only
errors; a field-level failure nulls that field and appends an error,
as the GraphQL spec requires — one bad resolver does not take down the
whole response.
Before anything resolves, the document is checked against
persistedQueries (a request may send only persistedQueryHash) and
the operation against limits; a refusal is the whole response.
authenticated is whether the caller authenticated the request, which
is what DVGraphQLIntrospection.authenticated asks.
Implementation
static Future<Map<String, Object?>> execute(
String document, {
Map<String, Object?>? variables,
String? operationName,
String? persistedQueryHash,
bool authenticated = false,
}) async {
final (persisted, manifestRefusal) =
_persistedDocument(document, persistedQueryHash);
if (manifestRefusal != null) return manifestRefusal;
document = persisted!;
_ParsedDocument parsed;
try {
parsed = _Parser(document).parseDocument();
} on FormatException catch (error) {
return <String, Object?>{
'errors': <Object?>[
<String, Object?>{'message': error.message},
],
};
}
final operation = parsed.operation(operationName);
if (operation == null) {
return <String, Object?>{
'errors': <Object?>[
<String, Object?>{
'message': operationName == null
? 'The document declares multiple operations; pass '
'operationName to choose one.'
: 'No operation named "$operationName" in the document.',
},
],
};
}
final roots = switch (operation.kind) {
'query' => _queries,
'mutation' => _mutations,
_ => null,
};
if (roots == null) {
return <String, Object?>{
'errors': <Object?>[
<String, Object?>{
'message': 'A ${operation.kind} operation yields a stream of '
'results; use DVGraphQL.subscribe instead of execute.',
},
],
};
}
final refusal = _edgeRefusal(
parsed,
operation,
variables ?? const <String, Object?>{},
authenticated: authenticated,
);
if (refusal != null) return refusal;
final errors = <Map<String, Object?>>[];
final context = _ExecutionContext(
variables: variables ?? const <String, Object?>{},
fragments: parsed.fragments,
errors: errors,
);
final data = <String, Object?>{};
// Expansion can fail on the request itself -- an unknown fragment, an
// unknown directive, a malformed @skip -- and that is a request error, not
// a field error: it belongs in `errors` where the client can read it,
// rather than escaping as an exception the caller has to catch.
final List<_Selection> selections;
try {
selections = context.expand(operation.selections).toList();
} on FormatException catch (error) {
return <String, Object?>{
'errors': <Object?>[
<String, Object?>{'message': error.message},
],
};
}
for (final selection in selections) {
// Introspection resolves from the registry rather than a resolver, so
// tooling works against whatever the application registered.
if (selection.name == '__schema') {
data[selection.alias] = context.project(
introspectionSchema(),
selection,
);
continue;
}
if (selection.name == '__type') {
final name = context.argument(selection, 'name');
final types =
introspectionSchema()['types']! as List<Object?>;
Map<String, Object?>? match;
for (final type in types) {
if ((type! as Map)['name'] == name) {
match = (type as Map).cast<String, Object?>();
break;
}
}
data[selection.alias] =
match == null ? null : context.project(match, selection);
continue;
}
if (selection.name == '__typename') {
data[selection.alias] =
operation.kind == 'mutation' ? 'Mutation' : 'Query';
continue;
}
final field = roots[selection.name];
if (field == null) {
errors.add(<String, Object?>{
'message':
'Unknown ${operation.kind} field "${selection.name}".',
});
data[selection.alias] = null;
continue;
}
data[selection.alias] =
await context.resolveField(field, selection, null, root: true);
}
return <String, Object?>{
'data': data,
if (errors.isNotEmpty) 'errors': errors,
};
}