Add support for global variables in GraphQL

This commit is contained in:
Tobe O 2018-08-04 15:54:18 -04:00
parent 532f698aa2
commit 15e1d18224
4 changed files with 67 additions and 33 deletions

View file

@ -22,6 +22,11 @@ final Validator graphQlPostBody = new Validator({
/// https://graphql.org/learn/serving-over-http/ /// https://graphql.org/learn/serving-over-http/
RequestHandler graphQLHttp(GraphQL graphQL) { RequestHandler graphQLHttp(GraphQL graphQL) {
return (req, res) async { return (req, res) async {
var globalVariables = <String, dynamic>{
'__requestctx': req,
'__responsectx': res,
};
executeMap(Map map) async { executeMap(Map map) async {
var text = req.body['query'] as String; var text = req.body['query'] as String;
var operationName = req.body['operation_name'] as String; var operationName = req.body['operation_name'] as String;
@ -37,6 +42,7 @@ RequestHandler graphQLHttp(GraphQL graphQL) {
sourceUrl: 'input', sourceUrl: 'input',
operationName: operationName, operationName: operationName,
variableValues: foldToStringDynamic(variables as Map), variableValues: foldToStringDynamic(variables as Map),
globalVariables: globalVariables,
), ),
}; };
} }
@ -50,7 +56,11 @@ RequestHandler graphQLHttp(GraphQL graphQL) {
if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) { if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) {
var text = utf8.decode(await req.lazyOriginalBuffer()); var text = utf8.decode(await req.lazyOriginalBuffer());
return { return {
'data': await graphQL.parseAndExecute(text, sourceUrl: 'input') 'data': await graphQL.parseAndExecute(
text,
sourceUrl: 'input',
globalVariables: globalVariables,
),
}; };
} else if (req.headers.contentType?.mimeType == 'application/json') { } else if (req.headers.contentType?.mimeType == 'application/json') {
if (await validate(graphQlPostBody)(req, res)) { if (await validate(graphQlPostBody)(req, res)) {

View file

@ -1,5 +1,6 @@
import 'package:angel_model/angel_model.dart'; import 'package:angel_model/angel_model.dart';
import 'package:angel_serialize/angel_serialize.dart'; import 'package:angel_serialize/angel_serialize.dart';
import 'package:graphql_schema/graphql_schema.dart';
import 'character.dart'; import 'character.dart';
import 'episode.dart'; import 'episode.dart';
@ -7,6 +8,7 @@ import 'starship.dart';
@serializable @serializable
class Human extends Model implements Character { class Human extends Model implements Character {
@GraphQLDocumentation(description: "This human's name, of course.")
String name; String name;
List<Character> friends; List<Character> friends;
List<Episode> appearsIn; List<Episode> appearsIn;

View file

@ -23,7 +23,8 @@ class GraphQLObjectType
GraphQLObjectType(this.name, this.description, {this.isInterface: false}); GraphQLObjectType(this.name, this.description, {this.isInterface: false});
@override @override
GraphQLType<Map<String, dynamic>, Map<String, dynamic>> coerceToInputObject() { GraphQLType<Map<String, dynamic>, Map<String, dynamic>>
coerceToInputObject() {
return asInputObject('${name}Input', description: description); return asInputObject('${name}Input', description: description);
} }
@ -31,8 +32,9 @@ class GraphQLObjectType
GraphQLInputObjectType asInputObject(String name, {String description}) { GraphQLInputObjectType asInputObject(String name, {String description}) {
return new GraphQLInputObjectType(name, return new GraphQLInputObjectType(name,
description: description ?? this.description, description: description ?? this.description,
inputFields: inputFields: fields.map((f) => new GraphQLInputObjectField(
fields.map((f) => new GraphQLInputObjectField(f.name, f.type.coerceToInputObject()))); f.name, f.type.coerceToInputObject(),
description: f.description)));
} }
void inheritFrom(GraphQLObjectType other) { void inheritFrom(GraphQLObjectType other) {
@ -242,7 +244,8 @@ class GraphQLInputObjectType
} }
@override @override
GraphQLType<Map<String, dynamic>, Map<String, dynamic>> coerceToInputObject() => this; GraphQLType<Map<String, dynamic>, Map<String, dynamic>>
coerceToInputObject() => this;
} }
class GraphQLInputObjectField<Value, Serialized> { class GraphQLInputObjectField<Value, Serialized> {

View file

@ -73,7 +73,8 @@ class GraphQL {
{String operationName, {String operationName,
sourceUrl, sourceUrl,
Map<String, dynamic> variableValues: const {}, Map<String, dynamic> variableValues: const {},
initialValue}) { initialValue,
Map<String, dynamic> globalVariables}) {
var tokens = scan(text, sourceUrl: sourceUrl); var tokens = scan(text, sourceUrl: sourceUrl);
var parser = new Parser(tokens); var parser = new Parser(tokens);
var document = parser.parseDocument(); var document = parser.parseDocument();
@ -86,26 +87,31 @@ class GraphQL {
.toList()); .toList());
} }
return executeRequest(_schema, document, return executeRequest(
_schema,
document,
operationName: operationName, operationName: operationName,
initialValue: initialValue, initialValue: initialValue,
variableValues: variableValues); variableValues: variableValues,
globalVariables: globalVariables,
);
} }
Future<Map<String, dynamic>> executeRequest( Future<Map<String, dynamic>> executeRequest(
GraphQLSchema schema, DocumentContext document, GraphQLSchema schema, DocumentContext document,
{String operationName, {String operationName,
Map<String, dynamic> variableValues: const {}, Map<String, dynamic> variableValues: const {},
initialValue}) async { initialValue,
Map<String, dynamic> globalVariables}) async {
var operation = getOperation(document, operationName); var operation = getOperation(document, operationName);
var coercedVariableValues = var coercedVariableValues =
coerceVariableValues(schema, operation, variableValues ?? {}); coerceVariableValues(schema, operation, variableValues ?? {});
if (operation.isQuery) if (operation.isQuery)
return await executeQuery( return await executeQuery(document, operation, schema,
document, operation, schema, coercedVariableValues, initialValue); coercedVariableValues, initialValue, globalVariables);
else { else {
return executeMutation( return executeMutation(document, operation, schema, coercedVariableValues,
document, operation, schema, coercedVariableValues, initialValue); initialValue, globalVariables);
} }
} }
@ -175,11 +181,12 @@ class GraphQL {
OperationDefinitionContext query, OperationDefinitionContext query,
GraphQLSchema schema, GraphQLSchema schema,
Map<String, dynamic> variableValues, Map<String, dynamic> variableValues,
initialValue) async { initialValue,
Map<String, dynamic> globalVariables) async {
var queryType = schema.queryType; var queryType = schema.queryType;
var selectionSet = query.selectionSet; var selectionSet = query.selectionSet;
return await executeSelectionSet( return await executeSelectionSet(document, selectionSet, queryType,
document, selectionSet, queryType, initialValue, variableValues); initialValue, variableValues, globalVariables);
} }
Future<Map<String, dynamic>> executeMutation( Future<Map<String, dynamic>> executeMutation(
@ -187,7 +194,8 @@ class GraphQL {
OperationDefinitionContext mutation, OperationDefinitionContext mutation,
GraphQLSchema schema, GraphQLSchema schema,
Map<String, dynamic> variableValues, Map<String, dynamic> variableValues,
initialValue) async { initialValue,
Map<String, dynamic> globalVariables) async {
var mutationType = schema.mutationType; var mutationType = schema.mutationType;
if (mutationType == null) { if (mutationType == null) {
@ -196,8 +204,8 @@ class GraphQL {
} }
var selectionSet = mutation.selectionSet; var selectionSet = mutation.selectionSet;
return await executeSelectionSet( return await executeSelectionSet(document, selectionSet, mutationType,
document, selectionSet, mutationType, initialValue, variableValues); initialValue, variableValues, globalVariables);
} }
Future<Map<String, dynamic>> executeSelectionSet( Future<Map<String, dynamic>> executeSelectionSet(
@ -205,7 +213,8 @@ class GraphQL {
SelectionSetContext selectionSet, SelectionSetContext selectionSet,
GraphQLObjectType objectType, GraphQLObjectType objectType,
objectValue, objectValue,
Map<String, dynamic> variableValues) async { Map<String, dynamic> variableValues,
Map<String, dynamic> globalVariables) async {
var groupedFieldSet = var groupedFieldSet =
collectFields(document, objectType, selectionSet, variableValues); collectFields(document, objectType, selectionSet, variableValues);
var resultMap = <String, dynamic>{}; var resultMap = <String, dynamic>{};
@ -224,8 +233,16 @@ class GraphQL {
.firstWhere((f) => f.name == fieldName, orElse: () => null) .firstWhere((f) => f.name == fieldName, orElse: () => null)
?.type; ?.type;
if (fieldType == null) continue; if (fieldType == null) continue;
responseValue = await executeField(document, fieldName, objectType, responseValue = await executeField(
objectValue, fields, fieldType, variableValues); document,
fieldName,
objectType,
objectValue,
fields,
fieldType,
new Map<String, dynamic>.from(globalVariables)
..addAll(variableValues),
globalVariables);
} }
resultMap[responseKey] = responseValue; resultMap[responseKey] = responseValue;
@ -242,14 +259,15 @@ class GraphQL {
objectValue, objectValue,
List<SelectionContext> fields, List<SelectionContext> fields,
GraphQLType fieldType, GraphQLType fieldType,
Map<String, dynamic> variableValues) async { Map<String, dynamic> variableValues,
Map<String, dynamic> globalVariables) async {
var field = fields[0]; var field = fields[0];
var argumentValues = var argumentValues =
coerceArgumentValues(objectType, field, variableValues); coerceArgumentValues(objectType, field, variableValues);
var resolvedValue = await resolveFieldValue( var resolvedValue = await resolveFieldValue(
objectType, objectValue, field.field.fieldName.name, argumentValues); objectType, objectValue, field.field.fieldName.name, argumentValues);
return completeValue( return completeValue(document, fieldName, fieldType, fields, resolvedValue,
document, fieldName, fieldType, fields, resolvedValue, variableValues); variableValues, globalVariables);
} }
Map<String, dynamic> coerceArgumentValues(GraphQLObjectType objectType, Map<String, dynamic> coerceArgumentValues(GraphQLObjectType objectType,
@ -365,11 +383,12 @@ class GraphQL {
GraphQLType fieldType, GraphQLType fieldType,
List<SelectionContext> fields, List<SelectionContext> fields,
result, result,
Map<String, dynamic> variableValues) async { Map<String, dynamic> variableValues,
Map<String, dynamic> globalVariables) async {
if (fieldType is GraphQLNonNullableType) { if (fieldType is GraphQLNonNullableType) {
var innerType = fieldType.ofType; var innerType = fieldType.ofType;
var completedResult = completeValue( var completedResult = completeValue(document, fieldName, innerType,
document, fieldName, innerType, fields, result, variableValues); fields, result, variableValues, globalVariables);
if (completedResult == null) { if (completedResult == null) {
throw new GraphQLException.fromMessage( throw new GraphQLException.fromMessage(
@ -394,7 +413,7 @@ class GraphQL {
for (var resultItem in (result as Iterable)) { for (var resultItem in (result as Iterable)) {
out.add(await completeValue(document, '(item in "$fieldName")', out.add(await completeValue(document, '(item in "$fieldName")',
innerType, fields, resultItem, variableValues)); innerType, fields, resultItem, variableValues, globalVariables));
} }
return out; return out;
@ -425,8 +444,8 @@ class GraphQL {
} }
var subSelectionSet = mergeSelectionSets(fields); var subSelectionSet = mergeSelectionSets(fields);
return await executeSelectionSet( return await executeSelectionSet(document, subSelectionSet, objectType,
document, subSelectionSet, objectType, result, variableValues); result, variableValues, globalVariables);
} }
throw new UnsupportedError('Unsupported type: $fieldType'); throw new UnsupportedError('Unsupported type: $fieldType');