From 15e1d182247b1abbe4c3405d183e49a30cb7f605 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 4 Aug 2018 15:54:18 -0400 Subject: [PATCH] Add support for global variables in GraphQL --- angel_graphql/lib/src/graphql_http.dart | 12 +++- example_star_wars/lib/src/models/human.dart | 2 + graphql_schema/lib/src/object_type.dart | 11 +-- graphql_server/lib/graphql_server.dart | 75 +++++++++++++-------- 4 files changed, 67 insertions(+), 33 deletions(-) diff --git a/angel_graphql/lib/src/graphql_http.dart b/angel_graphql/lib/src/graphql_http.dart index f21f3fa9..7fe3ea2a 100644 --- a/angel_graphql/lib/src/graphql_http.dart +++ b/angel_graphql/lib/src/graphql_http.dart @@ -22,6 +22,11 @@ final Validator graphQlPostBody = new Validator({ /// https://graphql.org/learn/serving-over-http/ RequestHandler graphQLHttp(GraphQL graphQL) { return (req, res) async { + var globalVariables = { + '__requestctx': req, + '__responsectx': res, + }; + executeMap(Map map) async { var text = req.body['query'] as String; var operationName = req.body['operation_name'] as String; @@ -37,6 +42,7 @@ RequestHandler graphQLHttp(GraphQL graphQL) { sourceUrl: 'input', operationName: operationName, variableValues: foldToStringDynamic(variables as Map), + globalVariables: globalVariables, ), }; } @@ -50,7 +56,11 @@ RequestHandler graphQLHttp(GraphQL graphQL) { if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) { var text = utf8.decode(await req.lazyOriginalBuffer()); 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') { if (await validate(graphQlPostBody)(req, res)) { diff --git a/example_star_wars/lib/src/models/human.dart b/example_star_wars/lib/src/models/human.dart index 8e69bab4..d89aa1cf 100644 --- a/example_star_wars/lib/src/models/human.dart +++ b/example_star_wars/lib/src/models/human.dart @@ -1,5 +1,6 @@ import 'package:angel_model/angel_model.dart'; import 'package:angel_serialize/angel_serialize.dart'; +import 'package:graphql_schema/graphql_schema.dart'; import 'character.dart'; import 'episode.dart'; @@ -7,6 +8,7 @@ import 'starship.dart'; @serializable class Human extends Model implements Character { + @GraphQLDocumentation(description: "This human's name, of course.") String name; List friends; List appearsIn; diff --git a/graphql_schema/lib/src/object_type.dart b/graphql_schema/lib/src/object_type.dart index 4e3027ed..92a89fad 100644 --- a/graphql_schema/lib/src/object_type.dart +++ b/graphql_schema/lib/src/object_type.dart @@ -23,7 +23,8 @@ class GraphQLObjectType GraphQLObjectType(this.name, this.description, {this.isInterface: false}); @override - GraphQLType, Map> coerceToInputObject() { + GraphQLType, Map> + coerceToInputObject() { return asInputObject('${name}Input', description: description); } @@ -31,8 +32,9 @@ class GraphQLObjectType GraphQLInputObjectType asInputObject(String name, {String description}) { return new GraphQLInputObjectType(name, description: description ?? this.description, - inputFields: - fields.map((f) => new GraphQLInputObjectField(f.name, f.type.coerceToInputObject()))); + inputFields: fields.map((f) => new GraphQLInputObjectField( + f.name, f.type.coerceToInputObject(), + description: f.description))); } void inheritFrom(GraphQLObjectType other) { @@ -242,7 +244,8 @@ class GraphQLInputObjectType } @override - GraphQLType, Map> coerceToInputObject() => this; + GraphQLType, Map> + coerceToInputObject() => this; } class GraphQLInputObjectField { diff --git a/graphql_server/lib/graphql_server.dart b/graphql_server/lib/graphql_server.dart index f19b43cc..41ec0db3 100644 --- a/graphql_server/lib/graphql_server.dart +++ b/graphql_server/lib/graphql_server.dart @@ -73,7 +73,8 @@ class GraphQL { {String operationName, sourceUrl, Map variableValues: const {}, - initialValue}) { + initialValue, + Map globalVariables}) { var tokens = scan(text, sourceUrl: sourceUrl); var parser = new Parser(tokens); var document = parser.parseDocument(); @@ -86,26 +87,31 @@ class GraphQL { .toList()); } - return executeRequest(_schema, document, - operationName: operationName, - initialValue: initialValue, - variableValues: variableValues); + return executeRequest( + _schema, + document, + operationName: operationName, + initialValue: initialValue, + variableValues: variableValues, + globalVariables: globalVariables, + ); } Future> executeRequest( GraphQLSchema schema, DocumentContext document, {String operationName, Map variableValues: const {}, - initialValue}) async { + initialValue, + Map globalVariables}) async { var operation = getOperation(document, operationName); var coercedVariableValues = coerceVariableValues(schema, operation, variableValues ?? {}); if (operation.isQuery) - return await executeQuery( - document, operation, schema, coercedVariableValues, initialValue); + return await executeQuery(document, operation, schema, + coercedVariableValues, initialValue, globalVariables); else { - return executeMutation( - document, operation, schema, coercedVariableValues, initialValue); + return executeMutation(document, operation, schema, coercedVariableValues, + initialValue, globalVariables); } } @@ -175,11 +181,12 @@ class GraphQL { OperationDefinitionContext query, GraphQLSchema schema, Map variableValues, - initialValue) async { + initialValue, + Map globalVariables) async { var queryType = schema.queryType; var selectionSet = query.selectionSet; - return await executeSelectionSet( - document, selectionSet, queryType, initialValue, variableValues); + return await executeSelectionSet(document, selectionSet, queryType, + initialValue, variableValues, globalVariables); } Future> executeMutation( @@ -187,7 +194,8 @@ class GraphQL { OperationDefinitionContext mutation, GraphQLSchema schema, Map variableValues, - initialValue) async { + initialValue, + Map globalVariables) async { var mutationType = schema.mutationType; if (mutationType == null) { @@ -196,8 +204,8 @@ class GraphQL { } var selectionSet = mutation.selectionSet; - return await executeSelectionSet( - document, selectionSet, mutationType, initialValue, variableValues); + return await executeSelectionSet(document, selectionSet, mutationType, + initialValue, variableValues, globalVariables); } Future> executeSelectionSet( @@ -205,7 +213,8 @@ class GraphQL { SelectionSetContext selectionSet, GraphQLObjectType objectType, objectValue, - Map variableValues) async { + Map variableValues, + Map globalVariables) async { var groupedFieldSet = collectFields(document, objectType, selectionSet, variableValues); var resultMap = {}; @@ -224,8 +233,16 @@ class GraphQL { .firstWhere((f) => f.name == fieldName, orElse: () => null) ?.type; if (fieldType == null) continue; - responseValue = await executeField(document, fieldName, objectType, - objectValue, fields, fieldType, variableValues); + responseValue = await executeField( + document, + fieldName, + objectType, + objectValue, + fields, + fieldType, + new Map.from(globalVariables) + ..addAll(variableValues), + globalVariables); } resultMap[responseKey] = responseValue; @@ -242,14 +259,15 @@ class GraphQL { objectValue, List fields, GraphQLType fieldType, - Map variableValues) async { + Map variableValues, + Map globalVariables) async { var field = fields[0]; var argumentValues = coerceArgumentValues(objectType, field, variableValues); var resolvedValue = await resolveFieldValue( objectType, objectValue, field.field.fieldName.name, argumentValues); - return completeValue( - document, fieldName, fieldType, fields, resolvedValue, variableValues); + return completeValue(document, fieldName, fieldType, fields, resolvedValue, + variableValues, globalVariables); } Map coerceArgumentValues(GraphQLObjectType objectType, @@ -365,11 +383,12 @@ class GraphQL { GraphQLType fieldType, List fields, result, - Map variableValues) async { + Map variableValues, + Map globalVariables) async { if (fieldType is GraphQLNonNullableType) { var innerType = fieldType.ofType; - var completedResult = completeValue( - document, fieldName, innerType, fields, result, variableValues); + var completedResult = completeValue(document, fieldName, innerType, + fields, result, variableValues, globalVariables); if (completedResult == null) { throw new GraphQLException.fromMessage( @@ -394,7 +413,7 @@ class GraphQL { for (var resultItem in (result as Iterable)) { out.add(await completeValue(document, '(item in "$fieldName")', - innerType, fields, resultItem, variableValues)); + innerType, fields, resultItem, variableValues, globalVariables)); } return out; @@ -425,8 +444,8 @@ class GraphQL { } var subSelectionSet = mergeSelectionSets(fields); - return await executeSelectionSet( - document, subSelectionSet, objectType, result, variableValues); + return await executeSelectionSet(document, subSelectionSet, objectType, + result, variableValues, globalVariables); } throw new UnsupportedError('Unsupported type: $fieldType');