execute static method

Future<Map<String, Object?>> execute(
  1. String document, {
  2. Map<String, Object?>? variables,
  3. String? operationName,
  4. String? persistedQueryHash,
  5. bool authenticated = false,
})

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,
  };
}