platform/graphql_server/lib/graphql_server.dart

743 lines
25 KiB
Dart
Raw Normal View History

2018-08-02 13:53:47 +00:00
import 'dart:async';
2018-08-02 13:31:54 +00:00
import 'package:graphql_parser/graphql_parser.dart';
import 'package:graphql_schema/graphql_schema.dart';
2018-08-03 17:26:11 +00:00
import 'introspection.dart';
2018-08-02 13:31:54 +00:00
2019-04-11 17:46:04 +00:00
/// Transforms any [Map] into `Map<String, dynamic>`.
Map<String, dynamic> foldToStringDynamic(Map map) {
return map == null
? null
: map.keys.fold<Map<String, dynamic>>(
<String, dynamic>{}, (out, k) => out..[k.toString()] = map[k]);
}
2019-04-11 17:46:04 +00:00
/// A Dart implementation of a GraphQL server.
2018-08-02 13:31:54 +00:00
class GraphQL {
2019-04-11 17:46:04 +00:00
/// Any custom types to include in introspection information.
2018-08-04 00:57:38 +00:00
final List<GraphQLType> customTypes = [];
2019-04-11 17:46:04 +00:00
/// An optional callback that can be used to resolve fields from objects that are not [Map]s,
/// when the related field has no resolver.
final FutureOr<T> Function<T>(T, String, Map<String, dynamic>)
defaultFieldResolver;
2018-08-02 18:33:50 +00:00
GraphQLSchema _schema;
2018-08-02 13:31:54 +00:00
2018-08-04 00:57:38 +00:00
GraphQL(GraphQLSchema schema,
2019-04-24 17:06:14 +00:00
{bool introspect = true,
2019-04-11 17:46:04 +00:00
this.defaultFieldResolver,
2018-08-04 00:57:38 +00:00
List<GraphQLType> customTypes = const <GraphQLType>[]})
: _schema = schema {
if (customTypes?.isNotEmpty == true) {
this.customTypes.addAll(customTypes);
}
2018-08-02 18:33:50 +00:00
if (introspect) {
2018-08-02 19:22:16 +00:00
var allTypes = <GraphQLType>[];
2018-08-04 00:57:38 +00:00
allTypes.addAll(this.customTypes);
2018-08-02 19:22:16 +00:00
_schema = reflectSchema(_schema, allTypes);
2018-08-04 19:18:53 +00:00
for (var type in allTypes.toSet()) {
2018-08-04 00:57:38 +00:00
if (!this.customTypes.contains(type)) {
this.customTypes.add(type);
}
2018-08-02 19:22:16 +00:00
}
2018-08-02 18:33:50 +00:00
}
2018-08-04 19:18:53 +00:00
if (_schema.queryType != null) this.customTypes.add(_schema.queryType);
2019-08-14 04:48:18 +00:00
if (_schema.mutationType != null) {
2018-08-04 19:18:53 +00:00
this.customTypes.add(_schema.mutationType);
2019-08-14 04:48:18 +00:00
}
if (_schema.subscriptionType != null) {
2019-04-18 00:52:26 +00:00
this.customTypes.add(_schema.subscriptionType);
2019-08-14 04:48:18 +00:00
}
2018-08-02 13:31:54 +00:00
}
GraphQLType convertType(TypeContext ctx) {
if (ctx.listType != null) {
2019-08-14 14:59:18 +00:00
return GraphQLListType(convertType(ctx.listType.innerType));
2018-08-02 13:31:54 +00:00
} else if (ctx.typeName != null) {
switch (ctx.typeName.name) {
case 'Int':
return graphQLString;
case 'Float':
return graphQLFloat;
case 'String':
return graphQLString;
case 'Boolean':
return graphQLBoolean;
case 'ID':
return graphQLId;
case 'Date':
case 'DateTime':
return graphQLDate;
default:
2018-08-04 00:57:38 +00:00
return customTypes.firstWhere((t) => t.name == ctx.typeName.name,
2019-08-14 04:44:22 +00:00
orElse: () => throw ArgumentError(
2018-08-04 00:57:38 +00:00
'Unknown GraphQL type: "${ctx.typeName.name}"'));
2018-08-02 13:31:54 +00:00
}
} else {
2019-08-14 04:44:22 +00:00
throw ArgumentError('Invalid GraphQL type: "${ctx.span.text}"');
2018-08-02 13:31:54 +00:00
}
}
2019-03-29 19:34:27 +00:00
Future parseAndExecute(String text,
2018-08-02 15:17:14 +00:00
{String operationName,
sourceUrl,
2019-04-24 17:06:14 +00:00
Map<String, dynamic> variableValues = const {},
initialValue,
Map<String, dynamic> globalVariables}) {
2018-08-02 15:17:14 +00:00
var tokens = scan(text, sourceUrl: sourceUrl);
2019-08-14 04:44:22 +00:00
var parser = Parser(tokens);
2018-08-02 15:17:14 +00:00
var document = parser.parseDocument();
2018-08-04 19:18:53 +00:00
if (parser.errors.isNotEmpty) {
2019-08-14 04:44:22 +00:00
throw GraphQLException(parser.errors
.map((e) => GraphQLExceptionError(e.message, locations: [
GraphExceptionErrorLocation.fromSourceLocation(e.span.start)
2018-08-04 19:18:53 +00:00
]))
.toList());
}
return executeRequest(
_schema,
document,
operationName: operationName,
initialValue: initialValue,
variableValues: variableValues,
globalVariables: globalVariables,
);
2018-08-02 15:17:14 +00:00
}
2019-03-29 19:34:27 +00:00
Future executeRequest(GraphQLSchema schema, DocumentContext document,
2018-08-02 15:17:14 +00:00
{String operationName,
2019-04-24 17:06:14 +00:00
Map<String, dynamic> variableValues = const <String, dynamic>{},
initialValue,
2019-04-24 17:06:14 +00:00
Map<String, dynamic> globalVariables = const <String, dynamic>{}}) async {
2018-08-02 13:31:54 +00:00
var operation = getOperation(document, operationName);
var coercedVariableValues = coerceVariableValues(
schema, operation, variableValues ?? <String, dynamic>{});
2019-08-14 04:48:18 +00:00
if (operation.isQuery) {
return await executeQuery(document, operation, schema,
coercedVariableValues, initialValue, globalVariables);
2019-08-14 04:48:18 +00:00
} else if (operation.isSubscription) {
2019-03-29 19:34:27 +00:00
return await subscribe(document, operation, schema, coercedVariableValues,
globalVariables, initialValue);
} else {
return executeMutation(document, operation, schema, coercedVariableValues,
initialValue, globalVariables);
}
2018-08-02 13:31:54 +00:00
}
OperationDefinitionContext getOperation(
DocumentContext document, String operationName) {
2019-08-14 04:48:18 +00:00
var ops = document.definitions.whereType<OperationDefinitionContext>();
2018-08-02 13:31:54 +00:00
if (operationName == null) {
return ops.length == 1
2019-08-14 04:48:18 +00:00
? ops.first
2019-08-14 04:44:22 +00:00
: throw GraphQLException.fromMessage(
2018-08-02 15:17:14 +00:00
'This document does not define any operations.');
2018-08-02 13:31:54 +00:00
} else {
2019-08-14 04:48:18 +00:00
return ops.firstWhere((d) => d.name == operationName,
orElse: () => throw GraphQLException.fromMessage(
'Missing required operation "$operationName".'));
2018-08-02 13:31:54 +00:00
}
}
Map<String, dynamic> coerceVariableValues(
GraphQLSchema schema,
OperationDefinitionContext operation,
Map<String, dynamic> variableValues) {
var coercedValues = <String, dynamic>{};
var variableDefinitions =
operation.variableDefinitions?.variableDefinitions ?? [];
for (var variableDefinition in variableDefinitions) {
var variableName = variableDefinition.variable.name;
var variableType = variableDefinition.type;
var defaultValue = variableDefinition.defaultValue;
var value = variableValues[variableName];
if (value == null) {
if (defaultValue != null) {
2019-08-14 14:59:18 +00:00
coercedValues[variableName] =
defaultValue.value.computeValue(variableValues);
2018-08-02 13:31:54 +00:00
} else if (!variableType.isNullable) {
2019-08-14 04:44:22 +00:00
throw GraphQLException.fromSourceSpan(
2018-08-03 21:07:08 +00:00
'Missing required variable "$variableName".',
variableDefinition.span);
2018-08-02 13:31:54 +00:00
}
} else {
var type = convertType(variableType);
var validation = type.validate(variableName, value);
if (!validation.successful) {
2019-08-14 04:44:22 +00:00
throw GraphQLException(validation.errors
.map((e) => GraphQLExceptionError(e, locations: [
GraphExceptionErrorLocation.fromSourceLocation(
2018-08-03 21:07:08 +00:00
variableDefinition.span.start)
]))
.toList());
2018-08-02 13:31:54 +00:00
} else {
coercedValues[variableName] = type.deserialize(value);
}
}
}
return coercedValues;
}
Future<Map<String, dynamic>> executeQuery(
2018-08-02 13:31:54 +00:00
DocumentContext document,
OperationDefinitionContext query,
GraphQLSchema schema,
Map<String, dynamic> variableValues,
initialValue,
Map<String, dynamic> globalVariables) async {
2018-08-04 19:18:53 +00:00
var queryType = schema.queryType;
2018-08-02 13:31:54 +00:00
var selectionSet = query.selectionSet;
return await executeSelectionSet(document, selectionSet, queryType,
initialValue, variableValues, globalVariables);
2018-08-02 13:31:54 +00:00
}
2018-08-04 01:29:53 +00:00
Future<Map<String, dynamic>> executeMutation(
DocumentContext document,
OperationDefinitionContext mutation,
GraphQLSchema schema,
Map<String, dynamic> variableValues,
initialValue,
Map<String, dynamic> globalVariables) async {
2018-08-04 19:18:53 +00:00
var mutationType = schema.mutationType;
2018-08-04 01:29:53 +00:00
if (mutationType == null) {
2019-08-14 04:44:22 +00:00
throw GraphQLException.fromMessage(
2018-08-04 15:01:49 +00:00
'The schema does not define a mutation type.');
2018-08-04 01:29:53 +00:00
}
var selectionSet = mutation.selectionSet;
return await executeSelectionSet(document, selectionSet, mutationType,
initialValue, variableValues, globalVariables);
2018-08-04 01:29:53 +00:00
}
2019-03-29 19:34:27 +00:00
Future<Stream<Map<String, dynamic>>> subscribe(
DocumentContext document,
OperationDefinitionContext subscription,
GraphQLSchema schema,
Map<String, dynamic> variableValues,
Map<String, dynamic> globalVariables,
initialValue) async {
var sourceStream = await createSourceEventStream(
document, subscription, schema, variableValues, initialValue);
return mapSourceToResponseEvent(sourceStream, subscription, schema,
document, initialValue, variableValues, globalVariables);
}
Future<Stream> createSourceEventStream(
DocumentContext document,
OperationDefinitionContext subscription,
GraphQLSchema schema,
Map<String, dynamic> variableValues,
initialValue) {
var selectionSet = subscription.selectionSet;
var subscriptionType = schema.subscriptionType;
2019-08-14 04:48:18 +00:00
if (subscriptionType == null) {
2019-03-29 19:34:27 +00:00
throw GraphQLException.fromSourceSpan(
'The schema does not define a subscription type.', subscription.span);
2019-08-14 04:48:18 +00:00
}
2019-03-29 19:34:27 +00:00
var groupedFieldSet =
collectFields(document, subscriptionType, selectionSet, variableValues);
2019-08-14 04:48:18 +00:00
if (groupedFieldSet.length != 1) {
2019-03-29 19:34:27 +00:00
throw GraphQLException.fromSourceSpan(
'The grouped field set from this query must have exactly one entry.',
selectionSet.span);
2019-08-14 04:48:18 +00:00
}
2019-03-29 19:34:27 +00:00
var fields = groupedFieldSet.entries.first.value;
2019-04-11 16:49:21 +00:00
var fieldName = fields.first.field.fieldName.alias?.name ??
fields.first.field.fieldName.name;
2019-03-29 19:34:27 +00:00
var field = fields.first;
var argumentValues =
coerceArgumentValues(subscriptionType, field, variableValues);
return resolveFieldEventStream(
subscriptionType, initialValue, fieldName, argumentValues);
}
Stream<Map<String, dynamic>> mapSourceToResponseEvent(
Stream sourceStream,
OperationDefinitionContext subscription,
GraphQLSchema schema,
DocumentContext document,
initialValue,
Map<String, dynamic> variableValues,
Map<String, dynamic> globalVariables,
) async* {
await for (var event in sourceStream) {
yield await executeSubscriptionEvent(document, subscription, schema,
2019-04-18 00:52:26 +00:00
event, variableValues, globalVariables);
2019-03-29 19:34:27 +00:00
}
}
Future<Map<String, dynamic>> executeSubscriptionEvent(
DocumentContext document,
OperationDefinitionContext subscription,
GraphQLSchema schema,
initialValue,
Map<String, dynamic> variableValues,
2019-04-18 00:52:26 +00:00
Map<String, dynamic> globalVariables) async {
2019-03-29 19:34:27 +00:00
var selectionSet = subscription.selectionSet;
var subscriptionType = schema.subscriptionType;
2019-08-14 04:48:18 +00:00
if (subscriptionType == null) {
2019-03-29 19:34:27 +00:00
throw GraphQLException.fromSourceSpan(
'The schema does not define a subscription type.', subscription.span);
2019-08-14 04:48:18 +00:00
}
2019-03-29 19:34:27 +00:00
try {
var data = await executeSelectionSet(document, selectionSet,
subscriptionType, initialValue, variableValues, globalVariables);
2019-04-18 00:52:26 +00:00
return {'data': data};
2019-03-29 19:34:27 +00:00
} on GraphQLException catch (e) {
return {
'data': null,
'errors': [e.errors.map((e) => e.toJson()).toList()]
};
}
}
Future<Stream> resolveFieldEventStream(GraphQLObjectType subscriptionType,
rootValue, String fieldName, Map<String, dynamic> argumentValues) async {
var field = subscriptionType.fields.firstWhere((f) => f.name == fieldName,
orElse: () {
throw GraphQLException.fromMessage(
'No subscription field named "$fieldName" is defined.');
});
var resolver = field.resolve;
var result = await resolver(rootValue, argumentValues);
2019-08-14 04:48:18 +00:00
if (result is Stream) {
2019-03-29 19:34:27 +00:00
return result;
2019-08-14 04:48:18 +00:00
} else {
2019-03-29 19:34:27 +00:00
return Stream.fromIterable([result]);
2019-08-14 04:48:18 +00:00
}
2019-03-29 19:34:27 +00:00
}
2018-08-02 13:53:47 +00:00
Future<Map<String, dynamic>> executeSelectionSet(
2018-08-02 13:31:54 +00:00
DocumentContext document,
SelectionSetContext selectionSet,
GraphQLObjectType objectType,
objectValue,
Map<String, dynamic> variableValues,
Map<String, dynamic> globalVariables) async {
2018-08-02 13:31:54 +00:00
var groupedFieldSet =
2018-08-02 13:50:31 +00:00
collectFields(document, objectType, selectionSet, variableValues);
2018-08-02 13:31:54 +00:00
var resultMap = <String, dynamic>{};
for (var responseKey in groupedFieldSet.keys) {
var fields = groupedFieldSet[responseKey];
for (var field in fields) {
2019-04-11 16:49:21 +00:00
var fieldName =
field.field.fieldName.alias?.name ?? field.field.fieldName.name;
2018-08-04 19:41:40 +00:00
var responseValue;
if (fieldName == '__typename') {
responseValue = objectType.name;
} else {
var fieldType = objectType.fields
.firstWhere((f) => f.name == fieldName, orElse: () => null)
?.type;
if (fieldType == null) continue;
responseValue = await executeField(
document,
fieldName,
objectType,
objectValue,
fields,
fieldType,
2019-08-14 04:48:18 +00:00
Map<String, dynamic>.from(globalVariables ?? <String, dynamic>{})
..addAll(variableValues),
globalVariables);
2018-08-04 19:41:40 +00:00
}
2018-08-02 13:31:54 +00:00
resultMap[responseKey] = responseValue;
}
}
return resultMap;
}
2018-08-02 17:02:00 +00:00
Future executeField(
DocumentContext document,
String fieldName,
2018-08-02 13:31:54 +00:00
GraphQLObjectType objectType,
objectValue,
List<SelectionContext> fields,
GraphQLType fieldType,
Map<String, dynamic> variableValues,
Map<String, dynamic> globalVariables) async {
2018-08-02 13:31:54 +00:00
var field = fields[0];
var argumentValues =
2018-08-02 13:50:31 +00:00
coerceArgumentValues(objectType, field, variableValues);
2018-08-02 13:53:47 +00:00
var resolvedValue = await resolveFieldValue(
objectType,
objectValue,
fieldName,
Map<String, dynamic>.from(globalVariables ?? {})
..addAll(argumentValues ?? {}));
return completeValue(document, fieldName, fieldType, fields, resolvedValue,
variableValues, globalVariables);
2018-08-02 13:31:54 +00:00
}
Map<String, dynamic> coerceArgumentValues(GraphQLObjectType objectType,
SelectionContext field, Map<String, dynamic> variableValues) {
var coercedValues = <String, dynamic>{};
var argumentValues = field.field.arguments;
2019-04-11 16:49:21 +00:00
var fieldName =
field.field.fieldName.alias?.name ?? field.field.fieldName.name;
2019-04-18 00:52:26 +00:00
var desiredField = objectType.fields.firstWhere((f) => f.name == fieldName,
orElse: () => throw FormatException(
'${objectType.name} has no field named "$fieldName".'));
2018-08-04 19:18:53 +00:00
var argumentDefinitions = desiredField.inputs;
2018-08-02 13:50:31 +00:00
for (var argumentDefinition in argumentDefinitions) {
var argumentName = argumentDefinition.name;
var argumentType = argumentDefinition.type;
var defaultValue = argumentDefinition.defaultValue;
2019-08-14 14:59:18 +00:00
var argumentValue = argumentValues
.firstWhere((a) => a.name == argumentName, orElse: () => null);
2018-08-02 13:50:31 +00:00
if (argumentValue?.value is VariableContext) {
2019-08-14 14:59:18 +00:00
var variableName = (argumentValue.value as VariableContext).name;
2018-08-02 13:50:31 +00:00
var variableValue = variableValues[variableName];
if (variableValues.containsKey(variableName)) {
coercedValues[argumentName] = variableValue;
} else if (defaultValue != null || argumentDefinition.defaultsToNull) {
coercedValues[argumentName] = defaultValue;
} else if (argumentType is GraphQLNonNullableType) {
2019-08-14 04:44:22 +00:00
throw GraphQLException.fromSourceSpan(
2018-08-04 19:18:53 +00:00
'Missing value for argument "$argumentName" of field "$fieldName".',
2019-08-14 14:59:18 +00:00
argumentValue.value.span);
2018-08-02 18:33:50 +00:00
} else {
continue;
2018-08-02 13:50:31 +00:00
}
2019-08-14 14:59:18 +00:00
} else if (argumentValue == null) {
2018-08-02 13:50:31 +00:00
if (defaultValue != null || argumentDefinition.defaultsToNull) {
coercedValues[argumentName] = defaultValue;
} else if (argumentType is GraphQLNonNullableType) {
2019-08-14 04:44:22 +00:00
throw GraphQLException.fromMessage(
2018-08-04 19:18:53 +00:00
'Missing value for argument "$argumentName" of field "$fieldName".');
2018-08-02 18:33:50 +00:00
} else {
continue;
}
} else {
2018-08-04 19:18:53 +00:00
try {
var validation = argumentType.validate(
2019-08-14 14:59:18 +00:00
fieldName, argumentValue.value.computeValue(variableValues));
2018-08-04 19:18:53 +00:00
if (!validation.successful) {
var errors = <GraphQLExceptionError>[
2019-08-14 04:44:22 +00:00
GraphQLExceptionError(
2018-08-04 19:18:53 +00:00
'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
locations: [
2019-08-14 04:44:22 +00:00
GraphExceptionErrorLocation.fromSourceLocation(
2019-08-14 14:59:18 +00:00
argumentValue.value.span.start)
2018-08-04 19:18:53 +00:00
],
)
];
for (var error in validation.errors) {
errors.add(
2019-08-14 04:44:22 +00:00
GraphQLExceptionError(
2018-08-04 19:18:53 +00:00
error,
locations: [
2019-08-14 04:44:22 +00:00
GraphExceptionErrorLocation.fromSourceLocation(
2019-08-14 14:59:18 +00:00
argumentValue.value.span.start)
2018-08-04 19:18:53 +00:00
],
),
);
}
2019-08-14 04:44:22 +00:00
throw GraphQLException(errors);
2018-08-04 19:18:53 +00:00
} else {
var coercedValue = validation.value;
coercedValues[argumentName] = coercedValue;
}
} on TypeError catch (e) {
2019-08-14 04:44:22 +00:00
throw GraphQLException(<GraphQLExceptionError>[
GraphQLExceptionError(
2018-08-04 19:18:53 +00:00
'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
locations: [
2019-08-14 04:44:22 +00:00
GraphExceptionErrorLocation.fromSourceLocation(
2019-08-14 14:59:18 +00:00
argumentValue.value.span.start)
2018-08-04 19:18:53 +00:00
],
),
2019-08-14 04:44:22 +00:00
GraphQLExceptionError(
2018-08-04 19:18:53 +00:00
e.message.toString(),
locations: [
2019-08-14 04:44:22 +00:00
GraphExceptionErrorLocation.fromSourceLocation(
2019-08-14 14:59:18 +00:00
argumentValue.value.span.start)
2018-08-04 19:18:53 +00:00
],
),
]);
2018-08-02 13:50:31 +00:00
}
}
}
2018-08-02 13:31:54 +00:00
return coercedValues;
}
2018-08-02 13:53:47 +00:00
Future<T> resolveFieldValue<T>(GraphQLObjectType objectType, T objectValue,
2018-08-02 13:56:00 +00:00
String fieldName, Map<String, dynamic> argumentValues) async {
var field = objectType.fields.firstWhere((f) => f.name == fieldName);
2018-08-02 15:17:14 +00:00
2019-04-18 00:52:26 +00:00
if (objectValue is Map) {
return objectValue[fieldName] as T;
} else if (field.resolve == null) {
2019-08-14 04:48:18 +00:00
if (defaultFieldResolver != null) {
2019-04-11 17:46:04 +00:00
return await defaultFieldResolver(
objectValue, fieldName, argumentValues);
2019-08-14 04:48:18 +00:00
}
2019-04-11 17:46:04 +00:00
2018-08-02 15:17:14 +00:00
return null;
} else {
return await field.resolve(objectValue, argumentValues) as T;
}
2018-08-02 13:56:00 +00:00
}
2018-08-02 13:53:47 +00:00
Future completeValue(
DocumentContext document,
String fieldName,
GraphQLType fieldType,
List<SelectionContext> fields,
result,
Map<String, dynamic> variableValues,
Map<String, dynamic> globalVariables) async {
if (fieldType is GraphQLNonNullableType) {
2018-08-04 19:18:53 +00:00
var innerType = fieldType.ofType;
2019-04-18 00:52:26 +00:00
var completedResult = await completeValue(document, fieldName, innerType,
fields, result, variableValues, globalVariables);
if (completedResult == null) {
2019-08-14 04:44:22 +00:00
throw GraphQLException.fromMessage(
'Null value provided for non-nullable field "$fieldName".');
} else {
return completedResult;
}
}
if (result == null) {
return null;
}
if (fieldType is GraphQLListType) {
if (result is! Iterable) {
2019-08-14 04:44:22 +00:00
throw GraphQLException.fromMessage(
2018-08-03 17:26:11 +00:00
'Value of field "$fieldName" must be a list or iterable, got $result instead.');
}
2018-08-04 19:18:53 +00:00
var innerType = fieldType.ofType;
2018-08-02 17:02:00 +00:00
var out = [];
for (var resultItem in (result as Iterable)) {
out.add(await completeValue(document, '(item in "$fieldName")',
innerType, fields, resultItem, variableValues, globalVariables));
2018-08-02 17:02:00 +00:00
}
return out;
}
if (fieldType is GraphQLScalarType) {
2018-08-03 17:45:40 +00:00
try {
var validation = fieldType.validate(fieldName, result);
2018-08-03 17:45:40 +00:00
if (!validation.successful) {
return null;
} else {
return validation.value;
}
} on TypeError {
2019-08-14 04:44:22 +00:00
throw GraphQLException.fromMessage(
2018-08-03 17:45:40 +00:00
'Value of field "$fieldName" must be ${fieldType.valueType}, got $result instead.');
}
}
if (fieldType is GraphQLObjectType || fieldType is GraphQLUnionType) {
GraphQLObjectType objectType;
if (fieldType is GraphQLObjectType && !fieldType.isInterface) {
objectType = fieldType;
} else {
2018-08-04 15:01:49 +00:00
objectType = resolveAbstractType(fieldName, fieldType, result);
}
2018-08-02 17:02:00 +00:00
var subSelectionSet = mergeSelectionSets(fields);
return await executeSelectionSet(document, subSelectionSet, objectType,
result, variableValues, globalVariables);
}
2019-08-14 04:44:22 +00:00
throw UnsupportedError('Unsupported type: $fieldType');
}
2018-08-04 19:18:53 +00:00
GraphQLObjectType resolveAbstractType(
String fieldName, GraphQLType type, result) {
List<GraphQLObjectType> possibleTypes;
if (type is GraphQLObjectType) {
2018-08-04 15:01:49 +00:00
if (type.isInterface) {
possibleTypes = type.possibleTypes;
} else {
return type;
}
} else if (type is GraphQLUnionType) {
possibleTypes = type.possibleTypes;
} else {
2019-08-14 04:44:22 +00:00
throw ArgumentError();
}
2018-08-04 15:01:49 +00:00
var errors = <GraphQLExceptionError>[];
for (var t in possibleTypes) {
try {
var validation =
2018-08-04 15:01:49 +00:00
t.validate(fieldName, foldToStringDynamic(result as Map));
if (validation.successful) {
return t;
}
2018-08-04 15:01:49 +00:00
2019-08-14 04:48:18 +00:00
errors.addAll(validation.errors.map((m) => GraphQLExceptionError(m)));
2019-04-11 17:05:47 +00:00
} on GraphQLException catch (e) {
errors.addAll(e.errors);
}
}
2019-08-14 04:48:18 +00:00
errors.insert(0,
GraphQLExceptionError('Cannot convert value $result to type $type.'));
2018-08-04 15:01:49 +00:00
2019-08-14 04:44:22 +00:00
throw GraphQLException(errors);
}
2018-08-02 17:02:00 +00:00
SelectionSetContext mergeSelectionSets(List<SelectionContext> fields) {
var selections = <SelectionContext>[];
for (var field in fields) {
if (field.field?.selectionSet != null) {
selections.addAll(field.field.selectionSet.selections);
} else if (field.inlineFragment?.selectionSet != null) {
selections.addAll(field.inlineFragment.selectionSet.selections);
}
}
2019-08-14 04:44:22 +00:00
return SelectionSetContext.merged(selections);
2018-08-02 17:02:00 +00:00
}
2018-08-02 13:31:54 +00:00
Map<String, List<SelectionContext>> collectFields(
DocumentContext document,
GraphQLObjectType objectType,
SelectionSetContext selectionSet,
Map<String, dynamic> variableValues,
2018-08-02 18:33:50 +00:00
{List visitedFragments}) {
2018-08-02 13:31:54 +00:00
var groupedFields = <String, List<SelectionContext>>{};
2018-08-02 18:33:50 +00:00
visitedFragments ??= [];
2018-08-02 13:31:54 +00:00
for (var selection in selectionSet.selections) {
2019-08-14 04:48:18 +00:00
if (getDirectiveValue('skip', 'if', selection, variableValues) == true) {
2018-08-02 13:31:54 +00:00
continue;
2019-08-14 04:48:18 +00:00
}
2018-08-02 13:31:54 +00:00
if (getDirectiveValue('include', 'if', selection, variableValues) ==
2019-08-14 04:48:18 +00:00
false) {
continue;
}
2018-08-02 13:31:54 +00:00
if (selection.field != null) {
2019-04-11 16:49:21 +00:00
var responseKey = selection.field.fieldName.alias?.alias ??
selection.field.fieldName.name;
2018-08-02 13:31:54 +00:00
var groupForResponseKey =
2018-08-02 13:50:31 +00:00
groupedFields.putIfAbsent(responseKey, () => []);
2018-08-02 13:31:54 +00:00
groupForResponseKey.add(selection);
} else if (selection.fragmentSpread != null) {
var fragmentSpreadName = selection.fragmentSpread.name;
if (visitedFragments.contains(fragmentSpreadName)) continue;
visitedFragments.add(fragmentSpreadName);
var fragment = document.definitions
2019-08-14 04:48:18 +00:00
.whereType<FragmentDefinitionContext>()
.firstWhere((f) => f.name == fragmentSpreadName,
orElse: () => null);
2018-08-02 13:31:54 +00:00
if (fragment == null) continue;
var fragmentType = fragment.typeCondition;
if (!doesFragmentTypeApply(objectType, fragmentType)) continue;
var fragmentSelectionSet = fragment.selectionSet;
var fragmentGroupFieldSet = collectFields(
document, objectType, fragmentSelectionSet, variableValues);
for (var responseKey in fragmentGroupFieldSet.keys) {
var fragmentGroup = fragmentGroupFieldSet[responseKey];
var groupForResponseKey =
2018-08-02 13:50:31 +00:00
groupedFields.putIfAbsent(responseKey, () => []);
2018-08-02 13:31:54 +00:00
groupForResponseKey.addAll(fragmentGroup);
}
} else if (selection.inlineFragment != null) {
var fragmentType = selection.inlineFragment.typeCondition;
if (fragmentType != null &&
!doesFragmentTypeApply(objectType, fragmentType)) continue;
var fragmentSelectionSet = selection.inlineFragment.selectionSet;
var fragmentGroupFieldSet = collectFields(
document, objectType, fragmentSelectionSet, variableValues);
for (var responseKey in fragmentGroupFieldSet.keys) {
var fragmentGroup = fragmentGroupFieldSet[responseKey];
var groupForResponseKey =
2018-08-02 13:50:31 +00:00
groupedFields.putIfAbsent(responseKey, () => []);
2018-08-02 13:31:54 +00:00
groupForResponseKey.addAll(fragmentGroup);
}
}
}
return groupedFields;
}
getDirectiveValue(String name, String argumentName,
SelectionContext selection, Map<String, dynamic> variableValues) {
if (selection.field == null) return null;
var directive = selection.field.directives.firstWhere((d) {
2019-08-14 14:59:18 +00:00
var vv = d.value;
if (vv is VariableContext) {
return vv.name == name;
} else {
return vv.computeValue(variableValues) == name;
}
2018-08-02 13:31:54 +00:00
}, orElse: () => null);
if (directive == null) return null;
if (directive.argument?.name != argumentName) return null;
2019-08-14 14:59:18 +00:00
var vv = directive.argument.value;
if (vv is VariableContext) {
var vname = vv.name;
if (!variableValues.containsKey(vname)) {
throw GraphQLException.fromSourceSpan(
'Unknown variable: "$vname"', vv.span);
}
return variableValues[vname];
2019-08-14 04:48:18 +00:00
}
2019-08-14 14:59:18 +00:00
return vv.computeValue(variableValues);
2018-08-02 13:31:54 +00:00
}
bool doesFragmentTypeApply(
GraphQLObjectType objectType, TypeConditionContext fragmentType) {
2019-08-14 04:44:22 +00:00
var type = convertType(TypeContext(fragmentType.typeName, null));
if (type is GraphQLObjectType && !type.isInterface) {
2019-08-14 04:48:18 +00:00
for (var field in type.fields) {
2018-08-02 13:31:54 +00:00
if (!objectType.fields.any((f) => f.name == field.name)) return false;
2019-08-14 04:48:18 +00:00
}
2018-08-02 13:31:54 +00:00
return true;
} else if (type is GraphQLObjectType && type.isInterface) {
return objectType.isImplementationOf(type);
} else if (type is GraphQLUnionType) {
return type.possibleTypes.any((t) => objectType.isImplementationOf(t));
2018-08-02 13:31:54 +00:00
}
return false;
}
}